@wdio/webdriver-mock-service 10.0.0-alpha.18 → 10.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/build/WebDriverMock.d.ts +30 -5
- package/build/WebDriverMock.d.ts.map +1 -1
- package/build/index.d.ts +5 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +109 -32
- package/package.json +9 -11
package/build/WebDriverMock.d.ts
CHANGED
|
@@ -1,20 +1,45 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type Interceptable } from 'undici';
|
|
2
2
|
export interface CommandMock {
|
|
3
|
-
[commandName: string]: (...args: unknown[]) =>
|
|
3
|
+
[commandName: string]: (...args: unknown[]) => MockInterceptor;
|
|
4
|
+
}
|
|
5
|
+
export interface MockInterceptor {
|
|
6
|
+
times(n: number): MockInterceptor;
|
|
7
|
+
once(): MockInterceptor;
|
|
8
|
+
twice(): MockInterceptor;
|
|
9
|
+
reply(statusCode: number, data?: unknown | (() => unknown)): void;
|
|
10
|
+
}
|
|
11
|
+
declare class UndiciMockInterceptor implements MockInterceptor {
|
|
12
|
+
#private;
|
|
13
|
+
private readonly pool;
|
|
14
|
+
private readonly opts;
|
|
15
|
+
constructor(pool: Interceptable, opts: {
|
|
16
|
+
path: (path: string) => boolean;
|
|
17
|
+
method: string;
|
|
18
|
+
body?: (body: Record<string, unknown>) => boolean;
|
|
19
|
+
});
|
|
20
|
+
times(n: number): this;
|
|
21
|
+
once(): this;
|
|
22
|
+
twice(): this;
|
|
23
|
+
reply(statusCode: number, data?: unknown | (() => unknown)): void;
|
|
4
24
|
}
|
|
5
25
|
export default class WebDriverMock {
|
|
26
|
+
#private;
|
|
6
27
|
path: string;
|
|
7
28
|
command: CommandMock;
|
|
8
|
-
scope: nock.Scope;
|
|
9
29
|
constructor(host?: string, port?: number, path?: string);
|
|
10
30
|
/**
|
|
11
31
|
* To allow random session IDs in url paths we have to set up a custom
|
|
12
32
|
* matcher that strips out the sessionID part from the expected url
|
|
13
33
|
* and actual url and replaces it with a constant session id
|
|
14
34
|
* @param {String} expectedPath path to match against
|
|
15
|
-
* @returns {Function} to be called by
|
|
35
|
+
* @returns {Function} to be called by the mock agent to match actual path
|
|
16
36
|
*/
|
|
17
37
|
static pathMatcher(expectedPath: string): (path: string) => boolean;
|
|
18
|
-
get(_obj: unknown, commandName: string): (...args: unknown[]) =>
|
|
38
|
+
get(_obj: unknown, commandName: string): (...args: unknown[]) => UndiciMockInterceptor;
|
|
39
|
+
/**
|
|
40
|
+
* Clear all interceptors and recreate the shared MockAgent.
|
|
41
|
+
*/
|
|
42
|
+
static reset(): void;
|
|
19
43
|
}
|
|
44
|
+
export {};
|
|
20
45
|
//# sourceMappingURL=WebDriverMock.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebDriverMock.d.ts","sourceRoot":"","sources":["../src/WebDriverMock.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"WebDriverMock.d.ts","sourceRoot":"","sources":["../src/WebDriverMock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuD,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAA;AAkBhG,MAAM,WAAW,WAAW;IACxB,CAAC,WAAW,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,eAAe,CAAA;CACjE;AAED,MAAM,WAAW,eAAe;IAC5B,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,eAAe,CAAA;IACjC,IAAI,IAAI,eAAe,CAAA;IACvB,KAAK,IAAI,eAAe,CAAA;IACxB,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,GAAG,IAAI,CAAA;CACpE;AA8BD,cAAM,qBAAsB,YAAW,eAAe;;IAI9C,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADJ,IAAI,EAAE,aAAa,EACnB,IAAI,EAAE;QACnB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;QAC/B,MAAM,EAAE,MAAM,CAAA;QACd,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAA;KACpD;IAGL,KAAK,CAAC,CAAC,EAAE,MAAM;IAKf,IAAI;IAIJ,KAAK;IAIL,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC;CA4B7D;AAED,MAAM,CAAC,OAAO,OAAO,aAAa;;IAKsC,IAAI,EAAE,MAAM;IAJhF,OAAO,EAAE,WAAW,CAAA;gBAIR,IAAI,GAAE,MAAoB,EAAE,IAAI,GAAE,MAAa,EAAS,IAAI,GAAE,MAAY;IAOtF;;;;;;OAMG;IACH,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO;IAqBnE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,IAG1B,GAAG,MAAM,OAAO,EAAE;IAwC9B;;OAEG;IACH,MAAM,CAAC,KAAK;CAYf"}
|
package/build/index.d.ts
CHANGED
|
@@ -18,13 +18,17 @@ export default class WebdriverMockService implements Services.ServiceInstance {
|
|
|
18
18
|
waitForExistScenario(): void;
|
|
19
19
|
asyncIterationScenario(): string[];
|
|
20
20
|
parentNextPreviousElementChaining(): void;
|
|
21
|
-
|
|
21
|
+
multiRemoteFetch(): void;
|
|
22
22
|
customCommandScenario(times?: number): void;
|
|
23
23
|
customSelectorScenario(): void;
|
|
24
24
|
waitForDisplayedScenario(): void;
|
|
25
25
|
cucumberScenario(): void;
|
|
26
26
|
executeMemLeakScenario(executeCalls: number): () => void;
|
|
27
27
|
requestRetryScenario(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Reset all mock interceptors and re-register the default command stubs.
|
|
30
|
+
* Kept as `nockReset` for compatibility with smoke tests that call it by name.
|
|
31
|
+
*/
|
|
28
32
|
nockReset(): void;
|
|
29
33
|
}
|
|
30
34
|
/**
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C,OAAO,aAAa,MAAM,oBAAoB,CAAA;AAW9C,MAAM,CAAC,OAAO,OAAO,oBAAqB,YAAW,QAAQ,CAAC,eAAe;IACzE,OAAO,CAAC,QAAQ,CAAC,CAAsD;IACvE,OAAO,CAAC,KAAK,CAAsB;;IAMnC,IAAI;IAiBJ,aAAa,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,GAAG,IAAI;IAK/C,MAAM,CACF,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,kBAAkB;IA4BjE,aAAa;IAQb,kBAAkB;IASlB,qBAAqB;IAKrB,sBAAsB;IAYtB,wBAAwB;IAUxB,6BAA6B;IAU7B,2BAA2B;IA+B3B,sBAAsB;IAgBtB,oBAAoB;IAQpB,sBAAsB;IAStB,iCAAiC;IAQjC,gBAAgB;IAShB,qBAAqB,CAAC,KAAK,SAAI;IAa/B,sBAAsB;IAUtB,wBAAwB;IASxB,gBAAgB;IAShB,sBAAsB,CAAC,YAAY,EAAE,MAAM;IAY3C,oBAAoB;IAOpB;;;OAGG;IACH,SAAS;CAKZ;AAED;;GAEG;AACH,OAAO,EAAE,aAAa,EAAE,CAAA;AAExB,eAAO,MAAM,QAAQ;;0BACC,WAAW,CAAC,MAAM;;CAavC,CAAA"}
|
package/build/index.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import nock2 from "nock";
|
|
3
|
-
import { v4 as uuidv4 } from "uuid";
|
|
4
|
-
|
|
5
1
|
// src/WebDriverMock.ts
|
|
6
|
-
import
|
|
2
|
+
import { MockAgent, setGlobalDispatcher, getGlobalDispatcher } from "undici";
|
|
7
3
|
import {
|
|
8
4
|
WebDriverProtocol,
|
|
9
|
-
MJsonWProtocol,
|
|
10
5
|
AppiumProtocol,
|
|
11
6
|
ChromiumProtocol,
|
|
12
7
|
SauceLabsProtocol,
|
|
@@ -16,7 +11,6 @@ var REGEXP_SESSION_ID = /\/[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0
|
|
|
16
11
|
var SESSION_ID = "XXX";
|
|
17
12
|
var protocols = [
|
|
18
13
|
WebDriverProtocol,
|
|
19
|
-
MJsonWProtocol,
|
|
20
14
|
AppiumProtocol,
|
|
21
15
|
ChromiumProtocol,
|
|
22
16
|
SauceLabsProtocol,
|
|
@@ -30,23 +24,79 @@ for (const protocol of protocols) {
|
|
|
30
24
|
}
|
|
31
25
|
}
|
|
32
26
|
}
|
|
27
|
+
var sharedAgent;
|
|
28
|
+
function configureNetConnect(agent) {
|
|
29
|
+
agent.disableNetConnect();
|
|
30
|
+
agent.enableNetConnect((host) => !String(host).endsWith(":4444"));
|
|
31
|
+
}
|
|
32
|
+
function getOrCreateAgent() {
|
|
33
|
+
if (!sharedAgent) {
|
|
34
|
+
sharedAgent = new MockAgent();
|
|
35
|
+
configureNetConnect(sharedAgent);
|
|
36
|
+
setGlobalDispatcher(sharedAgent);
|
|
37
|
+
}
|
|
38
|
+
return sharedAgent;
|
|
39
|
+
}
|
|
40
|
+
var UndiciMockInterceptor = class {
|
|
41
|
+
constructor(pool, opts) {
|
|
42
|
+
this.pool = pool;
|
|
43
|
+
this.opts = opts;
|
|
44
|
+
}
|
|
45
|
+
pool;
|
|
46
|
+
opts;
|
|
47
|
+
#times = null;
|
|
48
|
+
times(n) {
|
|
49
|
+
this.#times = n;
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
once() {
|
|
53
|
+
return this.times(1);
|
|
54
|
+
}
|
|
55
|
+
twice() {
|
|
56
|
+
return this.times(2);
|
|
57
|
+
}
|
|
58
|
+
reply(statusCode, data) {
|
|
59
|
+
const interceptOpts = {
|
|
60
|
+
path: this.opts.path,
|
|
61
|
+
method: this.opts.method
|
|
62
|
+
};
|
|
63
|
+
if (this.opts.body) {
|
|
64
|
+
interceptOpts.body = (body) => {
|
|
65
|
+
try {
|
|
66
|
+
const parsed = typeof body === "string" ? JSON.parse(body) : body;
|
|
67
|
+
return this.opts.body(parsed);
|
|
68
|
+
} catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const dataOrFn = typeof data === "function" ? () => data() : data;
|
|
74
|
+
const mock = this.pool.intercept(interceptOpts).reply(statusCode, dataOrFn);
|
|
75
|
+
if (this.#times === Infinity) {
|
|
76
|
+
mock.persist();
|
|
77
|
+
} else if (this.#times !== null && this.#times > 0) {
|
|
78
|
+
mock.times(this.#times);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
33
82
|
var WebDriverMock = class _WebDriverMock {
|
|
34
83
|
constructor(host = "localhost", port = 4444, path = "/") {
|
|
35
84
|
this.path = path;
|
|
36
|
-
this
|
|
37
|
-
|
|
38
|
-
|
|
85
|
+
this.#origin = `http://${host}:${port}`;
|
|
86
|
+
const agent = getOrCreateAgent();
|
|
87
|
+
this.#pool = agent.get(this.#origin);
|
|
39
88
|
this.command = new Proxy({}, { get: this.get.bind(this) });
|
|
40
89
|
}
|
|
41
90
|
path;
|
|
42
91
|
command;
|
|
43
|
-
|
|
92
|
+
#origin;
|
|
93
|
+
#pool;
|
|
44
94
|
/**
|
|
45
95
|
* To allow random session IDs in url paths we have to set up a custom
|
|
46
96
|
* matcher that strips out the sessionID part from the expected url
|
|
47
97
|
* and actual url and replaces it with a constant session id
|
|
48
98
|
* @param {String} expectedPath path to match against
|
|
49
|
-
* @returns {Function} to be called by
|
|
99
|
+
* @returns {Function} to be called by the mock agent to match actual path
|
|
50
100
|
*/
|
|
51
101
|
static pathMatcher(expectedPath) {
|
|
52
102
|
return (path) => {
|
|
@@ -54,9 +104,9 @@ var WebDriverMock = class _WebDriverMock {
|
|
|
54
104
|
if (!sessionId) {
|
|
55
105
|
return path === expectedPath;
|
|
56
106
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return
|
|
107
|
+
const normalizedExpected = expectedPath.replace(":sessionId", SESSION_ID);
|
|
108
|
+
const normalizedPath = path.replace(`${sessionId[0].slice(1)}`, SESSION_ID);
|
|
109
|
+
return normalizedPath === normalizedExpected;
|
|
60
110
|
};
|
|
61
111
|
}
|
|
62
112
|
get(_obj, commandName) {
|
|
@@ -66,24 +116,42 @@ var WebDriverMock = class _WebDriverMock {
|
|
|
66
116
|
for (const [i, param] of Object.entries(commandData.variables || [])) {
|
|
67
117
|
urlPath = urlPath.replace(`:${param.name}`, args[parseInt(i)]);
|
|
68
118
|
}
|
|
119
|
+
const path = _WebDriverMock.pathMatcher(urlPath);
|
|
69
120
|
if (method === "POST") {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
121
|
+
return new UndiciMockInterceptor(this.#pool, {
|
|
122
|
+
path,
|
|
123
|
+
method,
|
|
124
|
+
body: (body) => {
|
|
125
|
+
for (const param of commandData.parameters) {
|
|
126
|
+
if (!body[param.name]) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
if (param.required && typeof body[param.name] === "undefined") {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
78
132
|
}
|
|
133
|
+
return true;
|
|
79
134
|
}
|
|
80
|
-
return true;
|
|
81
135
|
});
|
|
82
136
|
}
|
|
83
|
-
|
|
84
|
-
return this.scope[reqMethod](_WebDriverMock.pathMatcher(urlPath));
|
|
137
|
+
return new UndiciMockInterceptor(this.#pool, { path, method });
|
|
85
138
|
};
|
|
86
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Clear all interceptors and recreate the shared MockAgent.
|
|
142
|
+
*/
|
|
143
|
+
static reset() {
|
|
144
|
+
const previous = sharedAgent;
|
|
145
|
+
sharedAgent = new MockAgent();
|
|
146
|
+
configureNetConnect(sharedAgent);
|
|
147
|
+
setGlobalDispatcher(sharedAgent);
|
|
148
|
+
if (previous) {
|
|
149
|
+
void previous.close();
|
|
150
|
+
}
|
|
151
|
+
if (getGlobalDispatcher().constructor.name !== "MockAgent") {
|
|
152
|
+
setGlobalDispatcher(sharedAgent);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
87
155
|
};
|
|
88
156
|
|
|
89
157
|
// src/constants.ts
|
|
@@ -138,7 +206,7 @@ var WebdriverMockService = class {
|
|
|
138
206
|
init() {
|
|
139
207
|
this._mock.command.status().times(Infinity).reply(200, { value: {} });
|
|
140
208
|
this._mock.command.newSession().times(Infinity).reply(200, () => {
|
|
141
|
-
newSession.value.sessionId =
|
|
209
|
+
newSession.value.sessionId = crypto.randomUUID();
|
|
142
210
|
return newSession;
|
|
143
211
|
});
|
|
144
212
|
this._mock.command.deleteSession().times(2).reply(200, deleteSession);
|
|
@@ -167,7 +235,7 @@ var WebdriverMockService = class {
|
|
|
167
235
|
this._browser.addCommand("clickScenario", this.clickScenario.bind(this));
|
|
168
236
|
this._browser.addCommand("isExistingScenario", this.isExistingScenario.bind(this));
|
|
169
237
|
this._browser.addCommand("isNotExistingScenario", this.isNotExistingScenario.bind(this));
|
|
170
|
-
this._browser.addCommand("
|
|
238
|
+
this._browser.addCommand("multiRemoteFetch", this.multiRemoteFetch.bind(this));
|
|
171
239
|
this._browser.addCommand("asyncIterationScenario", this.asyncIterationScenario.bind(this));
|
|
172
240
|
this._browser.addCommand("parentElementChaining", this.parentNextPreviousElementChaining.bind(this));
|
|
173
241
|
this._browser.addCommand("refetchElementScenario", this.refetchElementScenario.bind(this));
|
|
@@ -270,7 +338,7 @@ var WebdriverMockService = class {
|
|
|
270
338
|
this._mock.command.executeScript().reply(200, { value: elemParentResponse });
|
|
271
339
|
this._mock.command.getElementText(ELEMENT_REFETCHED).reply(200, { value: "some element text" });
|
|
272
340
|
}
|
|
273
|
-
|
|
341
|
+
multiRemoteFetch() {
|
|
274
342
|
const elemResponse = { [ELEM_PROP]: ELEMENT_ID };
|
|
275
343
|
const elem2Response = { [ELEM_PROP]: ELEMENT_REFETCHED };
|
|
276
344
|
this._mock.command.findElement().twice().reply(200, { value: elemResponse });
|
|
@@ -321,9 +389,13 @@ var WebdriverMockService = class {
|
|
|
321
389
|
this._mock.command.navigateTo().times(3).reply(500, {});
|
|
322
390
|
this._mock.command.navigateTo().reply(200, { value: null });
|
|
323
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Reset all mock interceptors and re-register the default command stubs.
|
|
394
|
+
* Kept as `nockReset` for compatibility with smoke tests that call it by name.
|
|
395
|
+
*/
|
|
324
396
|
nockReset() {
|
|
325
|
-
|
|
326
|
-
|
|
397
|
+
WebDriverMock.reset();
|
|
398
|
+
this._mock = new WebDriverMock();
|
|
327
399
|
this.init();
|
|
328
400
|
}
|
|
329
401
|
};
|
|
@@ -333,7 +405,12 @@ var launcher = class WebdriverMockLauncher {
|
|
|
333
405
|
config.port = 4444;
|
|
334
406
|
config.runnerEnv = {
|
|
335
407
|
...config.runnerEnv || {},
|
|
336
|
-
|
|
408
|
+
/**
|
|
409
|
+
* Opt workers into undici + MockAgent instead of native fetch + nock.
|
|
410
|
+
* `WDIO_UNIT_TESTS` alone still selects the web fetch implementation for
|
|
411
|
+
* Vitest unit tests that stub `globalThis.fetch`.
|
|
412
|
+
*/
|
|
413
|
+
WDIO_USE_UNDICI_MOCK: "true"
|
|
337
414
|
};
|
|
338
415
|
}
|
|
339
416
|
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wdio/webdriver-mock-service",
|
|
3
|
-
"version": "10.0.0
|
|
3
|
+
"version": "10.0.0",
|
|
4
4
|
"description": "A WebdriverIO service to stub all endpoints for internal testing purposes.",
|
|
5
5
|
"author": "Christian Bromann <mail@bromann.dev>",
|
|
6
6
|
"homepage": "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-webdriver-mock-service",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"engines": {
|
|
9
|
-
"node": ">=
|
|
9
|
+
"node": ">=22.19.0"
|
|
10
10
|
},
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -25,20 +25,18 @@
|
|
|
25
25
|
"import": "./build/index.js"
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
|
-
"typeScriptVersion": "
|
|
28
|
+
"typeScriptVersion": "6.0.3",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@
|
|
31
|
-
"@wdio/
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"uuid": "^11.1.0",
|
|
35
|
-
"webdriverio": "9.32.0"
|
|
30
|
+
"@wdio/protocols": "10.0.0",
|
|
31
|
+
"@wdio/types": "10.0.0",
|
|
32
|
+
"undici": "^8.11.2",
|
|
33
|
+
"webdriverio": "10.0.0"
|
|
36
34
|
},
|
|
37
35
|
"devDependencies": {
|
|
38
|
-
"@wdio/globals": "10.0.0
|
|
36
|
+
"@wdio/globals": "10.0.0"
|
|
39
37
|
},
|
|
40
38
|
"publishConfig": {
|
|
41
39
|
"access": "public"
|
|
42
40
|
},
|
|
43
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "38bd5ff4dccfaadccce4bd678286f00594623da2"
|
|
44
42
|
}
|