@wdio/nightwatch-devtools 1.0.0
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 +127 -0
- package/dist/constants.d.ts +57 -0
- package/dist/constants.js +97 -0
- package/dist/constants.js.map +1 -0
- package/dist/helpers/browserProxy.d.ts +46 -0
- package/dist/helpers/browserProxy.js +314 -0
- package/dist/helpers/browserProxy.js.map +1 -0
- package/dist/helpers/capturePerformance.d.ts +5 -0
- package/dist/helpers/capturePerformance.js +53 -0
- package/dist/helpers/capturePerformance.js.map +1 -0
- package/dist/helpers/cucumberHooks.cjs +54 -0
- package/dist/helpers/cucumberHooks.cjs.map +1 -0
- package/dist/helpers/cucumberHooks.d.cts +21 -0
- package/dist/helpers/suiteManager.d.ts +43 -0
- package/dist/helpers/suiteManager.js +143 -0
- package/dist/helpers/suiteManager.js.map +1 -0
- package/dist/helpers/testManager.d.ts +45 -0
- package/dist/helpers/testManager.js +143 -0
- package/dist/helpers/testManager.js.map +1 -0
- package/dist/helpers/utils.d.ts +83 -0
- package/dist/helpers/utils.js +442 -0
- package/dist/helpers/utils.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +796 -0
- package/dist/index.js.map +1 -0
- package/dist/reporter.d.ts +44 -0
- package/dist/reporter.js +185 -0
- package/dist/reporter.js.map +1 -0
- package/dist/session.d.ts +78 -0
- package/dist/session.js +643 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +190 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# @wdio/nightwatch-devtools
|
|
2
|
+
|
|
3
|
+
> Nightwatch adapter for [WebdriverIO DevTools](../../README.md) - brings the same visual debugging UI to your Nightwatch test suite with zero test code changes.
|
|
4
|
+
|
|
5
|
+
> **Note:** `@wdio/nightwatch-devtools` is included as a dependency of `@wdio/devtools-service`. Install the service package and import from `@wdio/nightwatch-devtools` directly.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
### Standard Nightwatch (mocha-style)
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
// nightwatch.conf.cjs
|
|
15
|
+
const nightwatchDevtools = require('@wdio/nightwatch-devtools').default
|
|
16
|
+
|
|
17
|
+
module.exports = {
|
|
18
|
+
src_folders: ['tests'],
|
|
19
|
+
|
|
20
|
+
test_settings: {
|
|
21
|
+
default: {
|
|
22
|
+
desiredCapabilities: {
|
|
23
|
+
browserName: 'chrome',
|
|
24
|
+
// Required for network request capture
|
|
25
|
+
'goog:loggingPrefs': { performance: 'ALL' }
|
|
26
|
+
},
|
|
27
|
+
globals: nightwatchDevtools({ port: 3000 })
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Run your tests as normal — the DevTools UI opens automatically in a new browser window:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
nightwatch
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
> No changes to your test files are needed.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
### Cucumber / BDD
|
|
44
|
+
|
|
45
|
+
Import `cucumberHooksPath` alongside the main export and pass it to the Cucumber `require` option. This registers `Before` / `After` scenario hooks that mirror the WebdriverIO service's `beforeScenario` / `afterScenario` behaviour.
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
// nightwatch.conf.cjs
|
|
49
|
+
const nightwatchDevtools = require('@wdio/nightwatch-devtools').default
|
|
50
|
+
const { cucumberHooksPath } = require('@wdio/nightwatch-devtools')
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
src_folders: ['features/step_definitions'],
|
|
54
|
+
|
|
55
|
+
test_runner: {
|
|
56
|
+
type: 'cucumber',
|
|
57
|
+
options: {
|
|
58
|
+
feature_path: 'features',
|
|
59
|
+
require: [cucumberHooksPath] // <-- register DevTools Cucumber hooks
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
test_settings: {
|
|
64
|
+
default: {
|
|
65
|
+
desiredCapabilities: {
|
|
66
|
+
browserName: 'chrome',
|
|
67
|
+
'goog:loggingPrefs': { performance: 'ALL' }
|
|
68
|
+
},
|
|
69
|
+
globals: nightwatchDevtools({ port: 3000 })
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Configuration Options
|
|
78
|
+
|
|
79
|
+
| Option | Type | Default | Description |
|
|
80
|
+
|--------|------|---------|-------------|
|
|
81
|
+
| `port` | `number` | `3000` | Port for the DevTools backend server. Auto-incremented if already in use. |
|
|
82
|
+
| `hostname` | `string` | `'localhost'` | Hostname the backend server binds to. |
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
globals: nightwatchDevtools({
|
|
86
|
+
port: 3000,
|
|
87
|
+
hostname: 'localhost'
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Examples
|
|
94
|
+
|
|
95
|
+
Working examples are included in this package:
|
|
96
|
+
|
|
97
|
+
| Directory | Runner | Command |
|
|
98
|
+
|-----------|--------|---------|
|
|
99
|
+
| [`example/`](./example) | Nightwatch mocha-style | `pnpm example` |
|
|
100
|
+
|
|
101
|
+
Build the package first:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# From repo root
|
|
105
|
+
pnpm build --filter @wdio/nightwatch-devtools
|
|
106
|
+
cd packages/nightwatch-devtools
|
|
107
|
+
pnpm example
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Limitations
|
|
113
|
+
|
|
114
|
+
Nightwatch does not provide the same depth of framework hooks as WebdriverIO, so there are a few differences from the WDIO DevTools service:
|
|
115
|
+
|
|
116
|
+
| Limitation | Detail |
|
|
117
|
+
|-----------|--------|
|
|
118
|
+
| No native command hooks | Nightwatch has no `beforeCommand` / `afterCommand` hook. Commands are intercepted via a browser proxy wrapper instead. |
|
|
119
|
+
| Limited test context | `browser.currentTest` provides less metadata than the WDIO runner context; test names and file paths require additional heuristics. |
|
|
120
|
+
| Flat suite nesting | Nightwatch does not natively support multiply-nested `describe` blocks; the plugin reports a maximum of two levels. |
|
|
121
|
+
| Delayed result availability | Test results are only finalised in `afterEach`, not available mid-test. |
|
|
122
|
+
|
|
123
|
+
Overall feature parity with the WebdriverIO DevTools service is approximately **80–90%**.
|
|
124
|
+
|
|
125
|
+
## :page_facing_up: License
|
|
126
|
+
|
|
127
|
+
[MIT](/LICENSE)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal Nightwatch commands to exclude from capture
|
|
3
|
+
*/
|
|
4
|
+
export declare const INTERNAL_COMMANDS_TO_IGNORE: readonly ["isAppiumClient", "isSafari", "isChrome", "isFirefox", "isEdge", "isMobile", "isAndroid", "isIOS", "session", "sessions", "timeouts", "timeoutsAsyncScript", "timeoutsImplicitWait", "getLog", "getLogTypes", "screenshot", "availableContexts", "currentContext", "setChromeOptions", "setDeviceName", "perform", "execute", "executeAsync", "executeScript", "sessionLog", "sessionLogTypes", "isLogAvailable", "end"];
|
|
5
|
+
export declare const CONSOLE_METHODS: readonly ["log", "info", "warn", "error"];
|
|
6
|
+
export declare const LOG_LEVEL_PATTERNS: ReadonlyArray<{
|
|
7
|
+
level: 'trace' | 'debug' | 'info' | 'warn' | 'error';
|
|
8
|
+
pattern: RegExp;
|
|
9
|
+
}>;
|
|
10
|
+
export declare const LOG_SOURCES: {
|
|
11
|
+
readonly BROWSER: "browser";
|
|
12
|
+
readonly TEST: "test";
|
|
13
|
+
readonly TERMINAL: "terminal";
|
|
14
|
+
};
|
|
15
|
+
export declare const ANSI_REGEX: RegExp;
|
|
16
|
+
export declare const DEFAULTS: {
|
|
17
|
+
readonly CID: "0-0";
|
|
18
|
+
readonly TEST_NAME: "unknown";
|
|
19
|
+
readonly FILE_NAME: "unknown";
|
|
20
|
+
readonly RETRIES: 0;
|
|
21
|
+
readonly DURATION: 0;
|
|
22
|
+
};
|
|
23
|
+
/** Timing constants (in milliseconds) */
|
|
24
|
+
export declare const TIMING: {
|
|
25
|
+
readonly UI_RENDER_DELAY: 150;
|
|
26
|
+
readonly TEST_START_DELAY: 100;
|
|
27
|
+
readonly SUITE_COMPLETE_DELAY: 200;
|
|
28
|
+
readonly UI_CONNECTION_WAIT: 10000;
|
|
29
|
+
readonly BROWSER_CLOSE_WAIT: 2000;
|
|
30
|
+
readonly INITIAL_CONNECTION_WAIT: 500;
|
|
31
|
+
readonly BROWSER_POLL_INTERVAL: 1000;
|
|
32
|
+
};
|
|
33
|
+
export declare const TEST_STATE: {
|
|
34
|
+
readonly PENDING: "pending";
|
|
35
|
+
readonly RUNNING: "running";
|
|
36
|
+
readonly PASSED: "passed";
|
|
37
|
+
readonly FAILED: "failed";
|
|
38
|
+
readonly SKIPPED: "skipped";
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Generic pattern matching Nightwatch commands whose result is a boolean.
|
|
42
|
+
*/
|
|
43
|
+
export declare const BOOLEAN_COMMAND_PATTERN: RegExp;
|
|
44
|
+
export declare const NAVIGATION_COMMANDS: readonly ["url", "navigate", "navigateTo"];
|
|
45
|
+
/** Spinner progress frames — suppress from UI Console output. */
|
|
46
|
+
export declare const SPINNER_RE: RegExp;
|
|
47
|
+
/** Matches a path segment that indicates a test/spec directory (e.g. /tests/, /spec/, /nightwatch/examples/, /features/). */
|
|
48
|
+
export declare const TEST_PATH_PATTERN: RegExp;
|
|
49
|
+
/** Matches file names that follow the *.test.ts / *.spec.js naming convention. */
|
|
50
|
+
export declare const TEST_FILE_PATTERN: RegExp;
|
|
51
|
+
/** Nightwatch config file names to search for, in priority order. */
|
|
52
|
+
export declare const CONFIG_FILENAMES: readonly ["nightwatch.conf.cjs", "nightwatch.conf.js", "nightwatch.conf.ts", "nightwatch.conf.mjs", "nightwatch.json"];
|
|
53
|
+
/**
|
|
54
|
+
* Global key used to share the plugin instance with Cucumber hooks.
|
|
55
|
+
* Must match across index.ts and cucumberHooks.cts.
|
|
56
|
+
*/
|
|
57
|
+
export declare const PLUGIN_GLOBAL_KEY = "__nightwatchDevtoolsPlugin";
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal Nightwatch commands to exclude from capture
|
|
3
|
+
*/
|
|
4
|
+
export const INTERNAL_COMMANDS_TO_IGNORE = [
|
|
5
|
+
'isAppiumClient',
|
|
6
|
+
'isSafari',
|
|
7
|
+
'isChrome',
|
|
8
|
+
'isFirefox',
|
|
9
|
+
'isEdge',
|
|
10
|
+
'isMobile',
|
|
11
|
+
'isAndroid',
|
|
12
|
+
'isIOS',
|
|
13
|
+
'session',
|
|
14
|
+
'sessions',
|
|
15
|
+
'timeouts',
|
|
16
|
+
'timeoutsAsyncScript',
|
|
17
|
+
'timeoutsImplicitWait',
|
|
18
|
+
'getLog',
|
|
19
|
+
'getLogTypes',
|
|
20
|
+
'screenshot',
|
|
21
|
+
'availableContexts',
|
|
22
|
+
'currentContext',
|
|
23
|
+
'setChromeOptions',
|
|
24
|
+
'setDeviceName',
|
|
25
|
+
'perform',
|
|
26
|
+
'execute',
|
|
27
|
+
'executeAsync',
|
|
28
|
+
'executeScript',
|
|
29
|
+
// Internal Nightwatch transport commands (used for log capture, not user actions)
|
|
30
|
+
'sessionLog',
|
|
31
|
+
'sessionLogTypes',
|
|
32
|
+
'isLogAvailable',
|
|
33
|
+
'end'
|
|
34
|
+
];
|
|
35
|
+
export const CONSOLE_METHODS = ['log', 'info', 'warn', 'error'];
|
|
36
|
+
export const LOG_LEVEL_PATTERNS = [
|
|
37
|
+
{ level: 'trace', pattern: /\btrace\b/i },
|
|
38
|
+
{ level: 'debug', pattern: /\bdebug\b/i },
|
|
39
|
+
{ level: 'info', pattern: /\binfo\b/i },
|
|
40
|
+
{ level: 'warn', pattern: /\bwarn(ing)?\b/i },
|
|
41
|
+
{ level: 'error', pattern: /\berror\b/i }
|
|
42
|
+
];
|
|
43
|
+
export const LOG_SOURCES = {
|
|
44
|
+
BROWSER: 'browser',
|
|
45
|
+
TEST: 'test',
|
|
46
|
+
TERMINAL: 'terminal'
|
|
47
|
+
};
|
|
48
|
+
export const ANSI_REGEX = /\x1b\[[?]?[0-9;]*[A-Za-z]/g;
|
|
49
|
+
export const DEFAULTS = {
|
|
50
|
+
CID: '0-0',
|
|
51
|
+
TEST_NAME: 'unknown',
|
|
52
|
+
FILE_NAME: 'unknown',
|
|
53
|
+
RETRIES: 0,
|
|
54
|
+
DURATION: 0
|
|
55
|
+
};
|
|
56
|
+
/** Timing constants (in milliseconds) */
|
|
57
|
+
export const TIMING = {
|
|
58
|
+
UI_RENDER_DELAY: 150,
|
|
59
|
+
TEST_START_DELAY: 100,
|
|
60
|
+
SUITE_COMPLETE_DELAY: 200,
|
|
61
|
+
UI_CONNECTION_WAIT: 10000,
|
|
62
|
+
BROWSER_CLOSE_WAIT: 2000,
|
|
63
|
+
INITIAL_CONNECTION_WAIT: 500,
|
|
64
|
+
BROWSER_POLL_INTERVAL: 1000
|
|
65
|
+
};
|
|
66
|
+
export const TEST_STATE = {
|
|
67
|
+
PENDING: 'pending',
|
|
68
|
+
RUNNING: 'running',
|
|
69
|
+
PASSED: 'passed',
|
|
70
|
+
FAILED: 'failed',
|
|
71
|
+
SKIPPED: 'skipped'
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Generic pattern matching Nightwatch commands whose result is a boolean.
|
|
75
|
+
*/
|
|
76
|
+
export const BOOLEAN_COMMAND_PATTERN = /^waitFor|^is[A-Z]|^has[A-Z]|(Visible|Present|Enabled|Selected|NotVisible|NotPresent)$/;
|
|
77
|
+
export const NAVIGATION_COMMANDS = ['url', 'navigate', 'navigateTo'];
|
|
78
|
+
/** Spinner progress frames — suppress from UI Console output. */
|
|
79
|
+
export const SPINNER_RE = /^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/u;
|
|
80
|
+
/** Matches a path segment that indicates a test/spec directory (e.g. /tests/, /spec/, /nightwatch/examples/, /features/). */
|
|
81
|
+
export const TEST_PATH_PATTERN = /\/(test|spec|tests|nightwatch|examples?|features|step-definitions)\//i;
|
|
82
|
+
/** Matches file names that follow the *.test.ts / *.spec.js naming convention. */
|
|
83
|
+
export const TEST_FILE_PATTERN = /\.(?:test|spec)\.[cm]?[jt]sx?$/i;
|
|
84
|
+
/** Nightwatch config file names to search for, in priority order. */
|
|
85
|
+
export const CONFIG_FILENAMES = [
|
|
86
|
+
'nightwatch.conf.cjs',
|
|
87
|
+
'nightwatch.conf.js',
|
|
88
|
+
'nightwatch.conf.ts',
|
|
89
|
+
'nightwatch.conf.mjs',
|
|
90
|
+
'nightwatch.json'
|
|
91
|
+
];
|
|
92
|
+
/**
|
|
93
|
+
* Global key used to share the plugin instance with Cucumber hooks.
|
|
94
|
+
* Must match across index.ts and cucumberHooks.cts.
|
|
95
|
+
*/
|
|
96
|
+
export const PLUGIN_GLOBAL_KEY = '__nightwatchDevtoolsPlugin';
|
|
97
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,gBAAgB;IAChB,UAAU;IACV,UAAU;IACV,WAAW;IACX,QAAQ;IACR,UAAU;IACV,WAAW;IACX,OAAO;IACP,SAAS;IACT,UAAU;IACV,UAAU;IACV,qBAAqB;IACrB,sBAAsB;IACtB,QAAQ;IACR,aAAa;IACb,YAAY;IACZ,mBAAmB;IACnB,gBAAgB;IAChB,kBAAkB;IAClB,eAAe;IACf,SAAS;IACT,SAAS;IACT,cAAc;IACd,eAAe;IACf,kFAAkF;IAClF,YAAY;IACZ,iBAAiB;IACjB,gBAAgB;IAChB,KAAK;CACG,CAAA;AAEV,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAU,CAAA;AAExE,MAAM,CAAC,MAAM,kBAAkB,GAG1B;IACH,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE;IACzC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE;IACzC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE;IACvC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,EAAE;IAC7C,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE;CACjC,CAAA;AAEV,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,OAAO,EAAE,SAAS;IAClB,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,UAAU;CACZ,CAAA;AAEV,MAAM,CAAC,MAAM,UAAU,GAAG,4BAA4B,CAAA;AAEtD,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,GAAG,EAAE,KAAK;IACV,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,SAAS;IACpB,OAAO,EAAE,CAAC;IACV,QAAQ,EAAE,CAAC;CACH,CAAA;AAEV,yCAAyC;AACzC,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,eAAe,EAAE,GAAG;IACpB,gBAAgB,EAAE,GAAG;IACrB,oBAAoB,EAAE,GAAG;IACzB,kBAAkB,EAAE,KAAK;IACzB,kBAAkB,EAAE,IAAI;IACxB,uBAAuB,EAAE,GAAG;IAC5B,qBAAqB,EAAE,IAAI;CACnB,CAAA;AAEV,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,SAAS;CACV,CAAA;AAEV;;GAEG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAClC,uFAAuF,CAAA;AAEzF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,UAAU,EAAE,YAAY,CAAU,CAAA;AAE7E,iEAAiE;AACjE,MAAM,CAAC,MAAM,UAAU,GAAG,gBAAgB,CAAA;AAE1C,6HAA6H;AAC7H,MAAM,CAAC,MAAM,iBAAiB,GAC5B,uEAAuE,CAAA;AAEzE,kFAAkF;AAClF,MAAM,CAAC,MAAM,iBAAiB,GAAG,iCAAiC,CAAA;AAElE,qEAAqE;AACrE,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,qBAAqB;IACrB,oBAAoB;IACpB,oBAAoB;IACpB,qBAAqB;IACrB,iBAAiB;CACT,CAAA;AAEV;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,4BAA4B,CAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Proxy
|
|
3
|
+
* Handles browser command interception and tracking
|
|
4
|
+
*/
|
|
5
|
+
import type { SessionCapturer } from '../session.js';
|
|
6
|
+
import type { TestManager } from './testManager.js';
|
|
7
|
+
import type { NightwatchBrowser } from '../types.js';
|
|
8
|
+
export declare class BrowserProxy {
|
|
9
|
+
private sessionCapturer;
|
|
10
|
+
private testManager;
|
|
11
|
+
private getCurrentTest;
|
|
12
|
+
/** Tracks which browser *instances* have already been proxied to avoid double-wrapping. */
|
|
13
|
+
private proxiedBrowsers;
|
|
14
|
+
private commandStack;
|
|
15
|
+
private lastCommandSig;
|
|
16
|
+
private currentTestFullPath;
|
|
17
|
+
/**
|
|
18
|
+
* Tracks the last captured command so that consecutive retries of the same
|
|
19
|
+
* command (e.g. getText inside a waitFor loop) overwrite the previous entry
|
|
20
|
+
* rather than appending, showing only the final execution result.
|
|
21
|
+
*/
|
|
22
|
+
private lastCapturedSig;
|
|
23
|
+
private lastCapturedId;
|
|
24
|
+
constructor(sessionCapturer: SessionCapturer, testManager: TestManager, getCurrentTest: () => any);
|
|
25
|
+
/**
|
|
26
|
+
* Update the session capturer reference after a WebDriver session change.
|
|
27
|
+
* Does NOT re-wrap browser methods — wrapping is permanent per browser object.
|
|
28
|
+
*/
|
|
29
|
+
updateSessionCapturer(capturer: SessionCapturer): void;
|
|
30
|
+
resetCommandTracking(): void;
|
|
31
|
+
getCurrentTestFullPath(): string | null;
|
|
32
|
+
setCurrentTestFullPath(path: string | null): void;
|
|
33
|
+
/**
|
|
34
|
+
* Wrap browser navigation methods (url / navigate / navigateTo) to inject
|
|
35
|
+
* the DevTools script after every navigation.
|
|
36
|
+
*
|
|
37
|
+
* Uses `browser` from the closure (not `this` inside perform) so it works
|
|
38
|
+
* for both standard Nightwatch (chainable API) and Cucumber async/await mode
|
|
39
|
+
* where `this` inside a perform callback is not the browser.
|
|
40
|
+
*/
|
|
41
|
+
wrapUrlMethod(browser: NightwatchBrowser): void;
|
|
42
|
+
wrapBrowserCommands(browser: NightwatchBrowser): void;
|
|
43
|
+
private handleCommandExecution;
|
|
44
|
+
private captureCommandError;
|
|
45
|
+
isProxied(browser: NightwatchBrowser): boolean;
|
|
46
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Proxy
|
|
3
|
+
* Handles browser command interception and tracking
|
|
4
|
+
*/
|
|
5
|
+
import logger from '@wdio/logger';
|
|
6
|
+
import { INTERNAL_COMMANDS_TO_IGNORE, BOOLEAN_COMMAND_PATTERN, NAVIGATION_COMMANDS } from '../constants.js';
|
|
7
|
+
import { getCallSourceFromStack } from './utils.js';
|
|
8
|
+
const log = logger('@wdio/nightwatch-devtools:browserProxy');
|
|
9
|
+
export class BrowserProxy {
|
|
10
|
+
sessionCapturer;
|
|
11
|
+
testManager;
|
|
12
|
+
getCurrentTest;
|
|
13
|
+
/** Tracks which browser *instances* have already been proxied to avoid double-wrapping. */
|
|
14
|
+
proxiedBrowsers = new WeakSet();
|
|
15
|
+
commandStack = [];
|
|
16
|
+
lastCommandSig = null;
|
|
17
|
+
currentTestFullPath = null;
|
|
18
|
+
/**
|
|
19
|
+
* Tracks the last captured command so that consecutive retries of the same
|
|
20
|
+
* command (e.g. getText inside a waitFor loop) overwrite the previous entry
|
|
21
|
+
* rather than appending, showing only the final execution result.
|
|
22
|
+
*/
|
|
23
|
+
lastCapturedSig = null;
|
|
24
|
+
lastCapturedId = null;
|
|
25
|
+
constructor(sessionCapturer, testManager, getCurrentTest) {
|
|
26
|
+
this.sessionCapturer = sessionCapturer;
|
|
27
|
+
this.testManager = testManager;
|
|
28
|
+
this.getCurrentTest = getCurrentTest;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Update the session capturer reference after a WebDriver session change.
|
|
32
|
+
* Does NOT re-wrap browser methods — wrapping is permanent per browser object.
|
|
33
|
+
*/
|
|
34
|
+
updateSessionCapturer(capturer) {
|
|
35
|
+
this.sessionCapturer = capturer;
|
|
36
|
+
}
|
|
37
|
+
resetCommandTracking() {
|
|
38
|
+
this.commandStack = [];
|
|
39
|
+
this.lastCommandSig = null;
|
|
40
|
+
this.lastCapturedSig = null;
|
|
41
|
+
this.lastCapturedId = null;
|
|
42
|
+
}
|
|
43
|
+
getCurrentTestFullPath() {
|
|
44
|
+
return this.currentTestFullPath;
|
|
45
|
+
}
|
|
46
|
+
setCurrentTestFullPath(path) {
|
|
47
|
+
this.currentTestFullPath = path;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Wrap browser navigation methods (url / navigate / navigateTo) to inject
|
|
51
|
+
* the DevTools script after every navigation.
|
|
52
|
+
*
|
|
53
|
+
* Uses `browser` from the closure (not `this` inside perform) so it works
|
|
54
|
+
* for both standard Nightwatch (chainable API) and Cucumber async/await mode
|
|
55
|
+
* where `this` inside a perform callback is not the browser.
|
|
56
|
+
*/
|
|
57
|
+
wrapUrlMethod(browser) {
|
|
58
|
+
const sessionCapturer = this.sessionCapturer;
|
|
59
|
+
const wrapNav = (methodName) => {
|
|
60
|
+
if (typeof browser[methodName] !== 'function') {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const original = browser[methodName].bind(browser);
|
|
64
|
+
browser[methodName] = function (...args) {
|
|
65
|
+
const result = original(...args);
|
|
66
|
+
const injectAndCapture = () => {
|
|
67
|
+
log.info(`[nav] ${methodName}(${args[0] ?? ''}) — injecting script`);
|
|
68
|
+
return sessionCapturer
|
|
69
|
+
.injectScript(browser)
|
|
70
|
+
.then(() => sessionCapturer.captureTrace(browser))
|
|
71
|
+
.catch((err) => log.error(`Failed to inject script: ${err.message}`));
|
|
72
|
+
};
|
|
73
|
+
if (result && typeof result.perform === 'function') {
|
|
74
|
+
// Standard Nightwatch (chained API): queue inside perform so it
|
|
75
|
+
// runs after navigation completes. Always pass `done` so the
|
|
76
|
+
// command queue is unblocked even if injection fails.
|
|
77
|
+
result.perform((done) => {
|
|
78
|
+
injectAndCapture().finally(() => done && done());
|
|
79
|
+
});
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
// Cucumber async/await: result is a Promise (or thenable).
|
|
83
|
+
// Return the AUGMENTED promise so that `await browser.url(...)` in
|
|
84
|
+
// the step definition waits for injectAndCapture to finish before
|
|
85
|
+
// Cucumber moves to the next step. Without this the injection races
|
|
86
|
+
// with the next step's commands (e.g. setValue), causing stale-element
|
|
87
|
+
// errors because the script-tag insertion mutates the DOM mid-form.
|
|
88
|
+
return Promise.resolve(result)
|
|
89
|
+
.then(injectAndCapture)
|
|
90
|
+
.catch(() => { });
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
wrapNav('url');
|
|
94
|
+
wrapNav('navigate');
|
|
95
|
+
wrapNav('navigateTo');
|
|
96
|
+
log.info('✓ Script injection wrapped');
|
|
97
|
+
}
|
|
98
|
+
wrapBrowserCommands(browser) {
|
|
99
|
+
if (this.proxiedBrowsers.has(browser)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const browserAny = browser;
|
|
103
|
+
const allMethods = new Set([
|
|
104
|
+
...Object.keys(browser),
|
|
105
|
+
...Object.getOwnPropertyNames(Object.getPrototypeOf(browser))
|
|
106
|
+
]);
|
|
107
|
+
const wrappedMethods = [];
|
|
108
|
+
allMethods.forEach((methodName) => {
|
|
109
|
+
if (methodName === 'constructor' ||
|
|
110
|
+
typeof browserAny[methodName] !== 'function') {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (INTERNAL_COMMANDS_TO_IGNORE.includes(methodName) ||
|
|
114
|
+
methodName.startsWith('__')) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const originalMethod = browserAny[methodName].bind(browser);
|
|
118
|
+
browserAny[methodName] = (...args) => {
|
|
119
|
+
return this.handleCommandExecution(browser, browserAny, methodName, originalMethod, args);
|
|
120
|
+
};
|
|
121
|
+
wrappedMethods.push(methodName);
|
|
122
|
+
});
|
|
123
|
+
this.proxiedBrowsers.add(browser);
|
|
124
|
+
log.info(`✓ Wrapped ${wrappedMethods.length} browser methods`);
|
|
125
|
+
}
|
|
126
|
+
handleCommandExecution(browser, browserAny, methodName, originalMethod, args) {
|
|
127
|
+
const currentNightwatchTest = browserAny.currentTest;
|
|
128
|
+
const currentTestName = this.testManager.detectTestBoundary(currentNightwatchTest);
|
|
129
|
+
this.testManager.startTestIfPending(currentTestName);
|
|
130
|
+
const callInfo = getCallSourceFromStack();
|
|
131
|
+
if (callInfo.filePath && !this.currentTestFullPath) {
|
|
132
|
+
this.currentTestFullPath = callInfo.filePath;
|
|
133
|
+
}
|
|
134
|
+
const lastArg = args[args.length - 1];
|
|
135
|
+
const hasUserCallback = typeof lastArg === 'function';
|
|
136
|
+
const userCallback = hasUserCallback ? lastArg : null;
|
|
137
|
+
const logArgs = hasUserCallback ? args.slice(0, -1) : args;
|
|
138
|
+
// Check for duplicate commands (based on method + logical args)
|
|
139
|
+
const cmdSig = JSON.stringify({
|
|
140
|
+
command: methodName,
|
|
141
|
+
args: logArgs,
|
|
142
|
+
src: callInfo.callSource
|
|
143
|
+
});
|
|
144
|
+
const isDuplicate = this.lastCommandSig === cmdSig;
|
|
145
|
+
if (!isDuplicate) {
|
|
146
|
+
this.commandStack.push({
|
|
147
|
+
command: methodName,
|
|
148
|
+
callSource: callInfo.callSource,
|
|
149
|
+
signature: cmdSig
|
|
150
|
+
});
|
|
151
|
+
this.lastCommandSig = cmdSig;
|
|
152
|
+
}
|
|
153
|
+
const testAtCallTime = this.getCurrentTest();
|
|
154
|
+
const testUid = testAtCallTime?.uid;
|
|
155
|
+
const callSource = callInfo.callSource;
|
|
156
|
+
const commandTimestamp = Date.now();
|
|
157
|
+
/**
|
|
158
|
+
* Result-capturing callback — called by Nightwatch's async queue when the
|
|
159
|
+
* command completes. This is where we get the *actual* result value.
|
|
160
|
+
*/
|
|
161
|
+
const captureCallback = (callbackResult) => {
|
|
162
|
+
const stackFrame = this.commandStack[this.commandStack.length - 1];
|
|
163
|
+
if (stackFrame?.command === methodName &&
|
|
164
|
+
stackFrame.signature === cmdSig) {
|
|
165
|
+
this.commandStack.pop();
|
|
166
|
+
}
|
|
167
|
+
const isBooleanCommand = BOOLEAN_COMMAND_PATTERN.test(methodName);
|
|
168
|
+
let serializedResult = undefined;
|
|
169
|
+
if (callbackResult !== null && callbackResult !== undefined) {
|
|
170
|
+
if (typeof callbackResult === 'object' && 'passed' in callbackResult) {
|
|
171
|
+
// Nightwatch assertion object {passed, actual, expected, message}
|
|
172
|
+
serializedResult = callbackResult.passed
|
|
173
|
+
? true
|
|
174
|
+
: {
|
|
175
|
+
passed: false,
|
|
176
|
+
actual: callbackResult.actual,
|
|
177
|
+
expected: callbackResult.expected,
|
|
178
|
+
message: callbackResult.message
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
else if (typeof callbackResult === 'object' &&
|
|
182
|
+
'value' in callbackResult) {
|
|
183
|
+
const raw = callbackResult.value;
|
|
184
|
+
// Boolean-semantic command returning null → timed out / not found → false
|
|
185
|
+
serializedResult = raw === null && isBooleanCommand ? false : raw;
|
|
186
|
+
}
|
|
187
|
+
else if (typeof callbackResult !== 'function') {
|
|
188
|
+
try {
|
|
189
|
+
serializedResult = JSON.parse(JSON.stringify(callbackResult));
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
serializedResult = String(callbackResult);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const currentTest = this.getCurrentTest();
|
|
197
|
+
const effectiveUid = currentTest?.uid ?? testUid;
|
|
198
|
+
if (effectiveUid) {
|
|
199
|
+
const isRetry = cmdSig === this.lastCapturedSig && this.lastCapturedId !== null;
|
|
200
|
+
if (isRetry) {
|
|
201
|
+
// Same command fired again (internal retry) — replace the previous
|
|
202
|
+
// entry so only the final result appears in the UI.
|
|
203
|
+
const { entry, oldTimestamp } = this.sessionCapturer.replaceCommand(this.lastCapturedId, methodName, logArgs, serializedResult, undefined, effectiveUid, callSource, commandTimestamp);
|
|
204
|
+
this.lastCapturedId = entry._id ?? null;
|
|
205
|
+
this.sessionCapturer.sendReplaceCommand(oldTimestamp, entry);
|
|
206
|
+
const entryToScreenshot = entry;
|
|
207
|
+
const ts = entryToScreenshot.timestamp;
|
|
208
|
+
this.sessionCapturer
|
|
209
|
+
.takeScreenshotViaHttp(browser)
|
|
210
|
+
.then((screenshot) => {
|
|
211
|
+
if (screenshot) {
|
|
212
|
+
;
|
|
213
|
+
entryToScreenshot.screenshot = screenshot;
|
|
214
|
+
this.sessionCapturer.sendReplaceCommand(ts, entryToScreenshot);
|
|
215
|
+
log.info(`[screenshot] Attached to ${methodName} (retry)`);
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
.catch(() => { });
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
// New command — capture and track.
|
|
222
|
+
// captureCommand() pushes the entry to commandsLog synchronously
|
|
223
|
+
// before any async work (navigation perf capture), so we can grab
|
|
224
|
+
// the ID immediately after the call — before any microtask fires.
|
|
225
|
+
// This avoids the race where a Nightwatch retry callback executes
|
|
226
|
+
// before .then() sets lastCapturedId, causing missed dedup.
|
|
227
|
+
this.lastCapturedSig = cmdSig;
|
|
228
|
+
this.lastCapturedId = null;
|
|
229
|
+
this.sessionCapturer
|
|
230
|
+
.captureCommand(methodName, logArgs, serializedResult, undefined, effectiveUid, callSource, commandTimestamp)
|
|
231
|
+
.catch((err) => log.error(`Failed to capture ${methodName}: ${err.message}`));
|
|
232
|
+
const lastCommand = this.sessionCapturer.commandsLog[this.sessionCapturer.commandsLog.length - 1];
|
|
233
|
+
if (lastCommand) {
|
|
234
|
+
this.lastCapturedId = lastCommand._id ?? null;
|
|
235
|
+
this.sessionCapturer.sendCommand(lastCommand);
|
|
236
|
+
log.info(`[command] ${methodName}`);
|
|
237
|
+
}
|
|
238
|
+
const entryToScreenshot = lastCommand;
|
|
239
|
+
if (entryToScreenshot) {
|
|
240
|
+
const ts = entryToScreenshot.timestamp;
|
|
241
|
+
this.sessionCapturer
|
|
242
|
+
.takeScreenshotViaHttp(browser)
|
|
243
|
+
.then((screenshot) => {
|
|
244
|
+
if (screenshot) {
|
|
245
|
+
;
|
|
246
|
+
entryToScreenshot.screenshot = screenshot;
|
|
247
|
+
this.sessionCapturer.sendReplaceCommand(ts, entryToScreenshot);
|
|
248
|
+
log.info(`[screenshot] Attached to ${methodName}`);
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
.catch(() => { });
|
|
252
|
+
}
|
|
253
|
+
// After DOM-mutating commands, re-poll mutations from the injected
|
|
254
|
+
// script so the browser preview stays in sync. Use setTimeout to
|
|
255
|
+
// run OUTSIDE Nightwatch's current callback stack (safer queue-wise).
|
|
256
|
+
const isDomMutating = NAVIGATION_COMMANDS.includes(methodName) ||
|
|
257
|
+
[
|
|
258
|
+
'click',
|
|
259
|
+
'doubleClick',
|
|
260
|
+
'rightClick',
|
|
261
|
+
'setValue',
|
|
262
|
+
'clearValue',
|
|
263
|
+
'sendKeys',
|
|
264
|
+
'submitForm',
|
|
265
|
+
'back',
|
|
266
|
+
'forward',
|
|
267
|
+
'refresh'
|
|
268
|
+
].includes(methodName);
|
|
269
|
+
if (isDomMutating) {
|
|
270
|
+
setTimeout(() => {
|
|
271
|
+
this.sessionCapturer.captureTrace(browser).catch(() => { });
|
|
272
|
+
}, 200);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (userCallback) {
|
|
277
|
+
return userCallback(callbackResult);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const modifiedArgs = [...logArgs, captureCallback];
|
|
281
|
+
try {
|
|
282
|
+
const result = originalMethod(...modifiedArgs);
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
const stackFrame = this.commandStack[this.commandStack.length - 1];
|
|
287
|
+
if (stackFrame?.command === methodName &&
|
|
288
|
+
stackFrame.signature === cmdSig) {
|
|
289
|
+
this.commandStack.pop();
|
|
290
|
+
}
|
|
291
|
+
this.captureCommandError(methodName, logArgs, error, callSource);
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
captureCommandError(methodName, args, error, callSource) {
|
|
296
|
+
const currentTest = this.getCurrentTest();
|
|
297
|
+
if (!currentTest) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
301
|
+
log.error(`[command error] ${methodName}: ${errMsg}`);
|
|
302
|
+
this.sessionCapturer
|
|
303
|
+
.captureCommand(methodName, args, undefined, error instanceof Error ? error : new Error(String(error)), currentTest.uid, callSource)
|
|
304
|
+
.catch((err) => log.error(`Failed to capture ${methodName}: ${err.message}`));
|
|
305
|
+
const lastCommand = this.sessionCapturer.commandsLog[this.sessionCapturer.commandsLog.length - 1];
|
|
306
|
+
if (lastCommand) {
|
|
307
|
+
this.sessionCapturer.sendCommand(lastCommand);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
isProxied(browser) {
|
|
311
|
+
return this.proxiedBrowsers.has(browser);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=browserProxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browserProxy.js","sourceRoot":"","sources":["../../src/helpers/browserProxy.ts"],"names":[],"mappings":"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"}
|