@skyramp/skyramp 1.3.11 → 1.3.12
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/package.json +3 -3
- package/src/classes/MockV2.d.ts +17 -5
- package/src/classes/MockV2.js +49 -16
- package/src/classes/SkyrampClient.d.ts +10 -9
- package/src/classes/SkyrampClient.js +122 -20
- package/src/classes/SmartPlaywright.js +90 -0
- package/src/function.d.ts +14 -0
- package/src/function.js +146 -1
- package/src/index.js +4 -2
- package/src/utils.d.ts +8 -0
- package/src/utils.js +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyramp/skyramp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.12",
|
|
4
4
|
"description": "module for leveraging skyramp cli functionality",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"lint": "eslint 'src/**/*.js' 'src/**/*.ts' --fix",
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
"@aws-sdk/client-s3": "^3.812.0",
|
|
27
27
|
"fs": "^0.0.1-security",
|
|
28
28
|
"js-yaml": "^4.1.0",
|
|
29
|
-
"koffi": "2.
|
|
29
|
+
"koffi": "^2.15.0",
|
|
30
30
|
"zod": "^3.25.3"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
|
34
34
|
"@typescript-eslint/parser": "^6.14.0",
|
|
35
|
-
"eslint": "^8.
|
|
35
|
+
"eslint": "^8.57.1"
|
|
36
36
|
}
|
|
37
37
|
}
|
package/src/classes/MockV2.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export class MockV2 {
|
|
|
10
10
|
* The URL of the service to mock
|
|
11
11
|
*/
|
|
12
12
|
URL: string;
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
/**
|
|
15
15
|
* The path to mock (e.g., "/api/v1/products")
|
|
16
16
|
*/
|
|
@@ -20,7 +20,7 @@ export class MockV2 {
|
|
|
20
20
|
* The HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
21
21
|
*/
|
|
22
22
|
method: string;
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
/**
|
|
25
25
|
* The HTTP status code to return
|
|
26
26
|
*/
|
|
@@ -30,19 +30,30 @@ export class MockV2 {
|
|
|
30
30
|
* The response body to return
|
|
31
31
|
*/
|
|
32
32
|
responseBody: string;
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
/**
|
|
35
35
|
* Optional request body to match
|
|
36
36
|
*/
|
|
37
37
|
requestBody?: string | null;
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
/**
|
|
40
40
|
* Optional data override object
|
|
41
41
|
*/
|
|
42
42
|
dataOverride?: Record<string, unknown> | null;
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
45
|
+
* Optional client ID for mock identification
|
|
46
|
+
*/
|
|
47
|
+
clientID?: string | null;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates a new MockV2 instance using an options object
|
|
51
|
+
* @param options - Configuration options for the mock
|
|
52
|
+
*/
|
|
53
|
+
constructor(options: MockV2Options);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Creates a new MockV2 instance using positional arguments
|
|
46
57
|
* @param URL - The URL of the service to mock
|
|
47
58
|
* @param path - The path path to mock (e.g., "/api/v1/products")
|
|
48
59
|
* @param method - The HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
@@ -73,6 +84,7 @@ export class MockV2 {
|
|
|
73
84
|
response_body: string;
|
|
74
85
|
request_body?: string;
|
|
75
86
|
data_override?: Record<string, unknown>;
|
|
87
|
+
client_id?: string;
|
|
76
88
|
};
|
|
77
89
|
|
|
78
90
|
/**
|
package/src/classes/MockV2.js
CHANGED
|
@@ -5,23 +5,52 @@
|
|
|
5
5
|
class MockV2 {
|
|
6
6
|
/**
|
|
7
7
|
* Represents a mock object configuration for API endpoint mocking.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* @
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* @
|
|
15
|
-
*
|
|
8
|
+
* Supports both positional arguments and options object patterns.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* // Positional arguments
|
|
12
|
+
* new MockV2("http://localhost:8080", "/api/v1/products", 8080, "POST", 201, "{}")
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // Options object (preferred)
|
|
16
|
+
* new MockV2({
|
|
17
|
+
* url: "http://localhost:8080",
|
|
18
|
+
* path: "/api/v1/products",
|
|
19
|
+
* method: "POST",
|
|
20
|
+
* responseStatusCode: 201
|
|
21
|
+
* })
|
|
22
|
+
*
|
|
23
|
+
* @param {string|Object} URLOrOptions - URL string or options object
|
|
24
|
+
* @param {string} [path] - The endpoint path (positional only)
|
|
25
|
+
* @param {string} [method] - The HTTP method (positional only)
|
|
26
|
+
* @param {number} [statusCode] - The status code (positional only)
|
|
27
|
+
* @param {string} [responseBody] - The response body (positional only)
|
|
28
|
+
* @param {string} [requestBody] - Optional request body to match
|
|
29
|
+
* @param {Object} [dataOverride] - Optional data override object
|
|
16
30
|
*/
|
|
17
|
-
constructor(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
constructor(URLOrOptions, path, method, statusCode, responseBody, requestBody = null, dataOverride = null) {
|
|
32
|
+
// Handle options object pattern
|
|
33
|
+
if (typeof URLOrOptions === 'object' && URLOrOptions !== null) {
|
|
34
|
+
const options = URLOrOptions;
|
|
35
|
+
this.url = options.url || '';
|
|
36
|
+
this.path = options.path || '';
|
|
37
|
+
this.method = options.method || '';
|
|
38
|
+
this.statusCode = options.statusCode || 201;
|
|
39
|
+
this.responseBody = (options.body !== undefined ? options.body : options.responseBody) || '';
|
|
40
|
+
this.requestBody = options.requestBody || null;
|
|
41
|
+
this.dataOverride = options.dataOverride || null;
|
|
42
|
+
this.clientID = options.clientID || null;
|
|
43
|
+
} else {
|
|
44
|
+
// Handle positional arguments pattern
|
|
45
|
+
this.url = URLOrOptions;
|
|
46
|
+
this.path = path;
|
|
47
|
+
this.method = method;
|
|
48
|
+
this.statusCode = statusCode;
|
|
49
|
+
this.responseBody = responseBody;
|
|
50
|
+
this.requestBody = requestBody;
|
|
51
|
+
this.dataOverride = dataOverride;
|
|
52
|
+
this.clientID = null;
|
|
53
|
+
}
|
|
25
54
|
}
|
|
26
55
|
|
|
27
56
|
/**
|
|
@@ -45,6 +74,10 @@ class MockV2 {
|
|
|
45
74
|
result.data_override = this.dataOverride;
|
|
46
75
|
}
|
|
47
76
|
|
|
77
|
+
if (this.clientID !== null) {
|
|
78
|
+
result.client_id = this.clientID;
|
|
79
|
+
}
|
|
80
|
+
|
|
48
81
|
return result;
|
|
49
82
|
}
|
|
50
83
|
|
|
@@ -138,6 +138,13 @@ interface GenerateRestTestOptions {
|
|
|
138
138
|
unblock?: boolean;
|
|
139
139
|
entrypoint?: string;
|
|
140
140
|
chainingKey?: string;
|
|
141
|
+
parentRequestData?: Record<string, string> | string;
|
|
142
|
+
parentStatusCode?: Record<string, string> | string;
|
|
143
|
+
requestAware?: boolean;
|
|
144
|
+
providerMode?: boolean;
|
|
145
|
+
consumerMode?: boolean;
|
|
146
|
+
providerOutput?: string;
|
|
147
|
+
consumerOutput?: string;
|
|
141
148
|
}
|
|
142
149
|
|
|
143
150
|
interface GenerateRestMockOptions {
|
|
@@ -157,6 +164,7 @@ interface GenerateRestMockOptions {
|
|
|
157
164
|
responseStatusCode?: string;
|
|
158
165
|
force?: boolean;
|
|
159
166
|
deployDashboard?: boolean;
|
|
167
|
+
requestAware?: boolean;
|
|
160
168
|
formParams?: string;
|
|
161
169
|
pathParams?: string;
|
|
162
170
|
queryParams?: string;
|
|
@@ -174,11 +182,6 @@ interface SendScenarioOptions {
|
|
|
174
182
|
skipCertVerification?: boolean;
|
|
175
183
|
}
|
|
176
184
|
|
|
177
|
-
interface InitAgentOptions {
|
|
178
|
-
version: string;
|
|
179
|
-
entryPoint?: string;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
185
|
export declare class SkyrampClient {
|
|
183
186
|
constructor(
|
|
184
187
|
kubeconfigPath?: string,
|
|
@@ -250,6 +253,7 @@ export declare class SkyrampClient {
|
|
|
250
253
|
mockYamlContent: string
|
|
251
254
|
): Promise<void>;
|
|
252
255
|
applyMock(mock: MockV2 | MockV2[]): Promise<void>;
|
|
256
|
+
removeAllMocks(): Promise<void>;
|
|
253
257
|
testerStart(
|
|
254
258
|
namespace: string,
|
|
255
259
|
kubePath: string,
|
|
@@ -314,10 +318,7 @@ export declare class SkyrampClient {
|
|
|
314
318
|
|
|
315
319
|
/**
|
|
316
320
|
* Initializes the agent by checking and pulling required Docker images (executor and worker).
|
|
317
|
-
* @param {InitAgentOptions} options - The options for initializing the agent.
|
|
318
|
-
* @param {string} options.version - The version of the agent to initialize.
|
|
319
|
-
* @param {string} [options.entryPoint='vscode'] - The entry point identifier (e.g., 'vscode').
|
|
320
321
|
* @returns {Promise<string>} A promise that resolves with the initialization output message.
|
|
321
322
|
*/
|
|
322
|
-
initAgent(
|
|
323
|
+
initAgent(): Promise<string>;
|
|
323
324
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { promisify } = require('util');
|
|
1
2
|
const lib = require('../lib');
|
|
2
3
|
const koffi = require('koffi');
|
|
3
4
|
const TrafficConfig = require('./TrafficConfig');
|
|
@@ -46,16 +47,98 @@ const runTesterStartWrapper = lib.func('runTesterStartWrapper', testerInfoType,
|
|
|
46
47
|
const runTesterStartWrapperv1 = lib.func('runTesterStartWrapperWithGlobalHeaders', testerInfoType, ['string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'bool', 'bool', 'string', 'string', 'string']);
|
|
47
48
|
const applyMockDescriptionWrapper = lib.func('applyMockDescriptionWrapper', 'string', ['string', 'string', 'string', 'string', 'string', 'string', 'string', 'string']);
|
|
48
49
|
const applyMockObjectWrapper = lib.func('applyMockObjectWrapper', 'string', ['string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'bool']);
|
|
50
|
+
const removeMocksObjectWrapper = lib.func('removeMocksObjectWrapper', 'string', []);
|
|
49
51
|
// NPM only: for VS code extension use
|
|
50
52
|
const initTargetWrapper = lib.func('initTargetWrapper', 'string', ['string']);
|
|
51
53
|
const deployTargetWrapper = lib.func('deployTargetWrapper', 'string', ['string', 'string', 'string', 'string', 'string', 'string', 'bool']);
|
|
52
54
|
const deleteTargetWrapper = lib.func('deleteTargetWrapper', 'string', ['string', 'string', 'string', 'string', 'string']);
|
|
53
55
|
|
|
54
|
-
const generateRestTestWrapper = lib.func('generateRestTestWrapper', 'string', [
|
|
55
|
-
|
|
56
|
+
const generateRestTestWrapper = lib.func('generateRestTestWrapper', 'string', [
|
|
57
|
+
'string', // test
|
|
58
|
+
'string', // uri
|
|
59
|
+
'string', // methodX
|
|
60
|
+
'string', // language
|
|
61
|
+
'string', // framework
|
|
62
|
+
'string', // output
|
|
63
|
+
'string', // outputDir
|
|
64
|
+
'string', // runtime
|
|
65
|
+
'string', // dockerNetwork
|
|
66
|
+
'string', // dockerWorkerPort
|
|
67
|
+
'string', // k8sNamespace
|
|
68
|
+
'string', // k8sConfig
|
|
69
|
+
'string', // k8sContxt
|
|
70
|
+
'string', // authHeader
|
|
71
|
+
'string', // authType
|
|
72
|
+
'string', // requestData
|
|
73
|
+
'string', // responseData
|
|
74
|
+
'string', // responseStatusCode
|
|
75
|
+
'bool', // force
|
|
76
|
+
'bool', // deployDashboard
|
|
77
|
+
'string', // formParams
|
|
78
|
+
'string', // pathParams
|
|
79
|
+
'string', // queryParams
|
|
80
|
+
'string', // apiSchema
|
|
81
|
+
'string', // trafeFilePath
|
|
82
|
+
'string', // generateInclude
|
|
83
|
+
'string', // generateExclude
|
|
84
|
+
'string', // generateNoProxy
|
|
85
|
+
'bool', // generateInsecure
|
|
86
|
+
'string', // assertOption
|
|
87
|
+
'bool', // playwright
|
|
88
|
+
'string', // playwrightOutput
|
|
89
|
+
'string', // playwrightInput
|
|
90
|
+
'string', // playwrightViewportSize
|
|
91
|
+
'string', // playwrightStoragePath
|
|
92
|
+
'string', // playwrightSaveStoragePath
|
|
93
|
+
'string', // browser
|
|
94
|
+
'string', // device
|
|
95
|
+
'string', // loadCount
|
|
96
|
+
'string', // loadDuration
|
|
97
|
+
'string', // loadNumThreads
|
|
98
|
+
'string', // loadRampupDuration
|
|
99
|
+
'string', // loadRampupInterval
|
|
100
|
+
'string', // loadTargetRPS
|
|
101
|
+
'string', // rawTrace
|
|
102
|
+
'bool', // unblock
|
|
103
|
+
'string', // entrypoint
|
|
104
|
+
'string', // chainingKey
|
|
105
|
+
'string', // parentRequestData
|
|
106
|
+
'string', // parentStatusCode
|
|
107
|
+
'bool', // requestAware
|
|
108
|
+
'bool', // providerMode
|
|
109
|
+
'bool', // consumerMode
|
|
110
|
+
'string', // providerOutput (contract test)
|
|
111
|
+
'string', // consumerOutput (contract test)
|
|
112
|
+
'bool' // skipProvisionParents
|
|
113
|
+
]);
|
|
114
|
+
const generateRestMockWrapper = lib.func('generateRestMockWrapper', 'string', [
|
|
115
|
+
'string', // uri
|
|
116
|
+
'string', // methodX
|
|
117
|
+
'string', // language
|
|
118
|
+
'string', // framework
|
|
119
|
+
'string', // output
|
|
120
|
+
'string', // outputDir
|
|
121
|
+
'string', // runtime
|
|
122
|
+
'string', // dockerNetwork
|
|
123
|
+
'string', // dockerWorkerPort
|
|
124
|
+
'string', // k8sNamespace
|
|
125
|
+
'string', // k8sConfig
|
|
126
|
+
'string', // k8sContext
|
|
127
|
+
'string', // responseData
|
|
128
|
+
'string', // responseStatusCode
|
|
129
|
+
'bool', // force
|
|
130
|
+
'bool', // deployDashboard
|
|
131
|
+
'bool', // requestAware
|
|
132
|
+
'string', // formParams
|
|
133
|
+
'string', // pathParams
|
|
134
|
+
'string', // queryParams
|
|
135
|
+
'string', // apiSchema
|
|
136
|
+
'string', // traceFilePath
|
|
137
|
+
'string' // entryPoint
|
|
138
|
+
]);
|
|
56
139
|
const traceCollectWrapper = lib.func('traceCollectWrapper', 'string', ['string', 'string', 'bool', 'string', 'string']);
|
|
57
140
|
const analyzeOpenapiWrapper = lib.func('analyzeOpenapiWrapper', 'string', ['string', 'string']);
|
|
58
|
-
const initAgentWrapper = lib.func('initAgent', 'string', [
|
|
141
|
+
const initAgentWrapper = lib.func('initAgent', 'string', []);
|
|
59
142
|
|
|
60
143
|
// Load test scenario support
|
|
61
144
|
const sendScenarioWrapper = lib.func('sendScenarioWrapper', contractResponseType, ['string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'bool', 'bool', 'bool']);
|
|
@@ -471,6 +554,24 @@ class SkyrampClient {
|
|
|
471
554
|
});
|
|
472
555
|
}
|
|
473
556
|
|
|
557
|
+
/**
|
|
558
|
+
* Removes all mock configurations from the worker.
|
|
559
|
+
* @returns {Promise} A promise that resolves when all mocks are removed successfully
|
|
560
|
+
*/
|
|
561
|
+
async removeAllMocks() {
|
|
562
|
+
return new Promise((resolve, reject) => {
|
|
563
|
+
removeMocksObjectWrapper.async((err, res) => {
|
|
564
|
+
if (err) {
|
|
565
|
+
reject(err);
|
|
566
|
+
} else if (res) {
|
|
567
|
+
reject(new Error(res));
|
|
568
|
+
} else {
|
|
569
|
+
resolve();
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
474
575
|
async testerStart(namespace, kubePath, kubeContext, clusterName, address, scenario) {
|
|
475
576
|
const preparedScenario = scenario.prepareTestDescription();
|
|
476
577
|
const testDescription = createTestDescriptionFromScenario({ scenario: preparedScenario });
|
|
@@ -788,6 +889,13 @@ class SkyrampClient {
|
|
|
788
889
|
|
|
789
890
|
async generateRestTest(options) {
|
|
790
891
|
return new Promise((resolve, reject) => {
|
|
892
|
+
const parentRequestData = typeof options.parentRequestData === 'string'
|
|
893
|
+
? options.parentRequestData
|
|
894
|
+
: JSON.stringify(options.parentRequestData || {});
|
|
895
|
+
const parentStatusCode = typeof options.parentStatusCode === 'string'
|
|
896
|
+
? options.parentStatusCode
|
|
897
|
+
: JSON.stringify(options.parentStatusCode || {});
|
|
898
|
+
|
|
791
899
|
generateRestTestWrapper.async(
|
|
792
900
|
options.testType || "",
|
|
793
901
|
options.uri || "",
|
|
@@ -837,6 +945,14 @@ class SkyrampClient {
|
|
|
837
945
|
options.unblock || false,
|
|
838
946
|
options.entrypoint || "",
|
|
839
947
|
options.chainingKey || "",
|
|
948
|
+
parentRequestData || "",
|
|
949
|
+
parentStatusCode || "",
|
|
950
|
+
options.requestAware || false,
|
|
951
|
+
options.providerMode || true,
|
|
952
|
+
options.consumerMode || false,
|
|
953
|
+
options.providerOutput || "",
|
|
954
|
+
options.consumerOutput || "",
|
|
955
|
+
options.skipProvisionParents || true,
|
|
840
956
|
(err, res) => {
|
|
841
957
|
if (err) {
|
|
842
958
|
reject(err);
|
|
@@ -867,6 +983,7 @@ class SkyrampClient {
|
|
|
867
983
|
options.responseStatusCode || "",
|
|
868
984
|
options.force || false,
|
|
869
985
|
options.deployDashboard || false,
|
|
986
|
+
options.requestAware || false,
|
|
870
987
|
options.formParams || "",
|
|
871
988
|
options.pathParams || "",
|
|
872
989
|
options.queryParams || "",
|
|
@@ -1007,26 +1124,11 @@ class SkyrampClient {
|
|
|
1007
1124
|
|
|
1008
1125
|
/**
|
|
1009
1126
|
* Initializes the agent by checking and pulling required Docker images (executor and worker).
|
|
1010
|
-
* @param {Object} options - The options for initializing the agent.
|
|
1011
|
-
* @param {string} options.version - The version of the agent to initialize.
|
|
1012
|
-
* @param {string} [options.entryPoint='vscode'] - The entry point identifier (e.g., 'vscode').
|
|
1013
1127
|
* @returns {Promise<string>} A promise that resolves with the initialization output message.
|
|
1014
1128
|
*/
|
|
1015
|
-
async initAgent(
|
|
1129
|
+
async initAgent() {
|
|
1016
1130
|
await checkForUpdate("npm");
|
|
1017
|
-
return
|
|
1018
|
-
initAgentWrapper.async(
|
|
1019
|
-
options.version || "",
|
|
1020
|
-
options.entryPoint || "vscode",
|
|
1021
|
-
(err, res) => {
|
|
1022
|
-
if (err) {
|
|
1023
|
-
reject(err);
|
|
1024
|
-
} else {
|
|
1025
|
-
resolve(res);
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
);
|
|
1029
|
-
});
|
|
1131
|
+
return promisify(initAgentWrapper.async)();
|
|
1030
1132
|
}
|
|
1031
1133
|
}
|
|
1032
1134
|
|
|
@@ -724,6 +724,11 @@ class SkyrampPlaywrightLocator {
|
|
|
724
724
|
return this._skyrampPage.newSkyrampPlaywrightLocator(new_locator, null, null);
|
|
725
725
|
}
|
|
726
726
|
|
|
727
|
+
contentFrame() {
|
|
728
|
+
const frameLocator = this._locator.contentFrame();
|
|
729
|
+
return new SkyrampPlaywrightFrameLocator(this._skyrampPage, frameLocator);
|
|
730
|
+
}
|
|
731
|
+
|
|
727
732
|
page() {
|
|
728
733
|
return this._skyrampPage._page;
|
|
729
734
|
}
|
|
@@ -734,6 +739,91 @@ class SkyrampPlaywrightLocator {
|
|
|
734
739
|
}
|
|
735
740
|
}
|
|
736
741
|
|
|
742
|
+
class SkyrampPlaywrightFrameLocator {
|
|
743
|
+
constructor(skyrampPage, frameLocator) {
|
|
744
|
+
this._skyrampPage = skyrampPage
|
|
745
|
+
this._frameLocator = frameLocator
|
|
746
|
+
debug(`SkyrampPlaywrightFrameLocator instantiated for ${frameLocator}`)
|
|
747
|
+
return new Proxy(this, {
|
|
748
|
+
get(wrapper, prop, receiver) {
|
|
749
|
+
if (Reflect.has(wrapper, prop)) {
|
|
750
|
+
return Reflect.get(wrapper, prop, receiver);
|
|
751
|
+
}
|
|
752
|
+
const value = Reflect.get(wrapper._frameLocator, prop, wrapper._frameLocator);
|
|
753
|
+
if (typeof value === 'function') {
|
|
754
|
+
return value.bind(wrapper._frameLocator);
|
|
755
|
+
}
|
|
756
|
+
return value;
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
locator(selector, options) {
|
|
762
|
+
const originalLocator = this._frameLocator.locator(selector, options);
|
|
763
|
+
return this._skyrampPage.newSkyrampPlaywrightLocator(originalLocator, selector, options);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
getByRole(role, options) {
|
|
767
|
+
const originalLocator = this._frameLocator.getByRole(role, options);
|
|
768
|
+
return this._skyrampPage.newSkyrampPlaywrightLocator(originalLocator, role, options);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
getByText(text, options) {
|
|
772
|
+
const originalLocator = this._frameLocator.getByText(text, options);
|
|
773
|
+
return this._skyrampPage.newSkyrampPlaywrightLocator(originalLocator, text, options);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
getByLabel(label, options) {
|
|
777
|
+
const originalLocator = this._frameLocator.getByLabel(label, options);
|
|
778
|
+
return this._skyrampPage.newSkyrampPlaywrightLocator(originalLocator, label, options);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
getByTestId(testId, options) {
|
|
782
|
+
const originalLocator = this._frameLocator.getByTestId(testId);
|
|
783
|
+
return this._skyrampPage.newSkyrampPlaywrightLocator(originalLocator, testId, options);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
getByTitle(title, options) {
|
|
787
|
+
const originalLocator = this._frameLocator.getByTitle(title, options);
|
|
788
|
+
return this._skyrampPage.newSkyrampPlaywrightLocator(originalLocator, title, options);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
getByPlaceholder(placeholder, options) {
|
|
792
|
+
const originalLocator = this._frameLocator.getByPlaceholder(placeholder, options);
|
|
793
|
+
return this._skyrampPage.newSkyrampPlaywrightLocator(originalLocator, placeholder, options);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
getByAltText(alt, options) {
|
|
797
|
+
const originalLocator = this._frameLocator.getByAltText(alt, options);
|
|
798
|
+
return this._skyrampPage.newSkyrampPlaywrightLocator(originalLocator, alt, options);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
owner() {
|
|
802
|
+
const originalLocator = this._frameLocator.owner();
|
|
803
|
+
return this._skyrampPage.newSkyrampPlaywrightLocator(originalLocator, null, null);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
frameLocator(selector) {
|
|
807
|
+
const originalFrameLocator = this._frameLocator.frameLocator(selector);
|
|
808
|
+
return new SkyrampPlaywrightFrameLocator(this._skyrampPage, originalFrameLocator);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
nth(index) {
|
|
812
|
+
const originalFrameLocator = this._frameLocator.nth(index);
|
|
813
|
+
return new SkyrampPlaywrightFrameLocator(this._skyrampPage, originalFrameLocator);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
first() {
|
|
817
|
+
const originalFrameLocator = this._frameLocator.first();
|
|
818
|
+
return new SkyrampPlaywrightFrameLocator(this._skyrampPage, originalFrameLocator);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
last() {
|
|
822
|
+
const originalFrameLocator = this._frameLocator.last();
|
|
823
|
+
return new SkyrampPlaywrightFrameLocator(this._skyrampPage, originalFrameLocator);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
737
827
|
class SkyrampPlaywrightPage {
|
|
738
828
|
constructor(page, testInfo) {
|
|
739
829
|
checkForUpdate("npm").catch((error) => {
|
package/src/function.d.ts
CHANGED
|
@@ -18,3 +18,17 @@ export function checkStatusCode(
|
|
|
18
18
|
response: ResponseV2,
|
|
19
19
|
expectedStatus: string
|
|
20
20
|
): boolean;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Validates that a request payload matches the expected reference payload.
|
|
24
|
+
* The reference is a stripped version of the recorded payload (volatile/falsy
|
|
25
|
+
* values removed). Throws an Error with details if validation fails.
|
|
26
|
+
*
|
|
27
|
+
* @param payload - The actual request payload (e.g. from response.request().postDataJSON()).
|
|
28
|
+
* @param reference - The expected (stripped) reference payload.
|
|
29
|
+
* @throws {Error} If the payload does not match the reference.
|
|
30
|
+
*/
|
|
31
|
+
export function checkRequestPayload(
|
|
32
|
+
payload: object,
|
|
33
|
+
reference: object
|
|
34
|
+
): void;
|
package/src/function.js
CHANGED
|
@@ -41,6 +41,151 @@ const checkStatusCodeWrapper = lib.func('checkStatusCodeWrapper', 'int', ['int',
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Returns true if a value is "truthy" for payload comparison purposes.
|
|
46
|
+
* Truthy: true, non-empty string, non-zero number, non-empty object/array.
|
|
47
|
+
* Falsy: false, null, undefined, 0, "", empty object, empty array.
|
|
48
|
+
* @param {*} value
|
|
49
|
+
* @returns {boolean}
|
|
50
|
+
*/
|
|
51
|
+
function _isTruthy(value) {
|
|
52
|
+
if (value === null || value === undefined || value === false || value === 0 || value === '') {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (Array.isArray(value)) {
|
|
56
|
+
return value.length > 0 && value.some(elem => _isTruthy(elem));
|
|
57
|
+
}
|
|
58
|
+
if (typeof value === 'object') {
|
|
59
|
+
return Object.keys(value).length > 0;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Returns true if a field key indicates a server-generated ID.
|
|
66
|
+
* @param {string} key
|
|
67
|
+
* @returns {boolean}
|
|
68
|
+
*/
|
|
69
|
+
function _isVolatileKey(key) {
|
|
70
|
+
return key === 'id' || key.endsWith('_id');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Returns true if a string value matches a known volatile pattern.
|
|
75
|
+
* @param {string} s
|
|
76
|
+
* @returns {boolean}
|
|
77
|
+
*/
|
|
78
|
+
function _isVolatileValue(value) {
|
|
79
|
+
if (typeof value === 'string') {
|
|
80
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value) ||
|
|
81
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})$/.test(value) ||
|
|
82
|
+
/^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(value) ||
|
|
83
|
+
/^[0-9a-f]{24}$/i.test(value) ||
|
|
84
|
+
/^[0-9a-f]{32,}$/i.test(value) ||
|
|
85
|
+
/^https?:\/\/\S+\/[0-9a-f]{24,}/i.test(value);
|
|
86
|
+
}
|
|
87
|
+
if (typeof value === 'number') {
|
|
88
|
+
return value >= 1_000_000_000;
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Recursively compares an actual request payload against a stripped reference
|
|
95
|
+
* payload. Returns an array of path-annotated error strings.
|
|
96
|
+
*
|
|
97
|
+
* Rules:
|
|
98
|
+
* - Every field in reference must exist in actual with the same value.
|
|
99
|
+
* - Every truthy field in actual that is NOT in reference is an error,
|
|
100
|
+
* unless the key or value matches a known volatile pattern.
|
|
101
|
+
* - Arrays must have the same length; null reference elements are skipped (volatile).
|
|
102
|
+
*
|
|
103
|
+
* @param {*} actual
|
|
104
|
+
* @param {*} reference
|
|
105
|
+
* @param {string} [path]
|
|
106
|
+
* @returns {string[]}
|
|
107
|
+
*/
|
|
108
|
+
function _comparePayload(actual, reference, path) {
|
|
109
|
+
const loc = path || 'root';
|
|
110
|
+
const errors = [];
|
|
111
|
+
|
|
112
|
+
// Null reference element means volatile/falsy — skip
|
|
113
|
+
if (reference === null || reference === undefined) {
|
|
114
|
+
return errors;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (actual === null || actual === undefined) {
|
|
118
|
+
errors.push(`${loc}: expected ${JSON.stringify(reference)}, got ${actual}`);
|
|
119
|
+
return errors;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Array comparison
|
|
123
|
+
if (Array.isArray(reference)) {
|
|
124
|
+
if (!Array.isArray(actual)) {
|
|
125
|
+
errors.push(`${loc}: expected array, got ${typeof actual}`);
|
|
126
|
+
return errors;
|
|
127
|
+
}
|
|
128
|
+
if (actual.length !== reference.length) {
|
|
129
|
+
errors.push(`${loc}: expected array length ${reference.length}, got ${actual.length}`);
|
|
130
|
+
return errors;
|
|
131
|
+
}
|
|
132
|
+
for (let i = 0; i < reference.length; i++) {
|
|
133
|
+
if (reference[i] === null) continue; // volatile element — skip
|
|
134
|
+
errors.push(..._comparePayload(actual[i], reference[i], `${loc}[${i}]`));
|
|
135
|
+
}
|
|
136
|
+
return errors;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Object comparison
|
|
140
|
+
if (typeof reference === 'object') {
|
|
141
|
+
if (typeof actual !== 'object' || Array.isArray(actual)) {
|
|
142
|
+
errors.push(`${loc}: expected object, got ${Array.isArray(actual) ? 'array' : typeof actual}`);
|
|
143
|
+
return errors;
|
|
144
|
+
}
|
|
145
|
+
// Check all reference fields exist and match
|
|
146
|
+
for (const key of Object.keys(reference)) {
|
|
147
|
+
if (!(key in actual)) {
|
|
148
|
+
errors.push(`${loc}.${key}: missing (expected ${JSON.stringify(reference[key])})`);
|
|
149
|
+
} else {
|
|
150
|
+
errors.push(..._comparePayload(actual[key], reference[key], `${loc}.${key}`));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Check for unexpected truthy fields in actual (skip volatile keys/values)
|
|
154
|
+
for (const key of Object.keys(actual)) {
|
|
155
|
+
if (!(key in reference) && _isTruthy(actual[key]) && !_isVolatileKey(key) && !_isVolatileValue(actual[key])) {
|
|
156
|
+
errors.push(`${loc}.${key}: unexpected truthy field (value: ${JSON.stringify(actual[key])})`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return errors;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Primitive comparison
|
|
163
|
+
if (actual !== reference) {
|
|
164
|
+
errors.push(`${loc}: expected ${JSON.stringify(reference)}, got ${JSON.stringify(actual)}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return errors;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Validates that a request payload matches the expected reference payload.
|
|
172
|
+
* The reference is a stripped version of the recorded payload (volatile/falsy
|
|
173
|
+
* values removed). Throws an Error with details if validation fails.
|
|
174
|
+
*
|
|
175
|
+
* @param {object} payload - The actual request payload (e.g. from response.request().postDataJSON()).
|
|
176
|
+
* @param {object} reference - The expected (stripped) reference payload.
|
|
177
|
+
* @throws {Error} If the payload does not match the reference.
|
|
178
|
+
*/
|
|
179
|
+
function checkRequestPayload(payload, reference) {
|
|
180
|
+
const errors = _comparePayload(payload, reference, '');
|
|
181
|
+
if (errors.length > 0) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`Request payload validation failed:\n${errors.join('\n')}`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
44
188
|
module.exports = {
|
|
45
|
-
checkStatusCode
|
|
189
|
+
checkStatusCode,
|
|
190
|
+
checkRequestPayload,
|
|
46
191
|
};
|
package/src/index.js
CHANGED
|
@@ -17,8 +17,8 @@ const { AsyncScenario, AsyncRequest } = require('./classes/AsyncScenario');
|
|
|
17
17
|
const { LoadTestConfig } = require('./classes/LoadTestConfig');
|
|
18
18
|
const AsyncTestStatus = require('./classes/AsyncTestStatus');
|
|
19
19
|
const MockV2 = require('./classes/MockV2');
|
|
20
|
-
const { getValue, getResponseValue, checkSchema, iterate, pushToolEvent } = require('./utils');
|
|
21
|
-
const { checkStatusCode } = require('./function');
|
|
20
|
+
const { getValue, getResponseValue, checkSchema, iterate, pushToolEvent, getBaseUrl } = require('./utils');
|
|
21
|
+
const { checkStatusCode, checkRequestPayload } = require('./function');
|
|
22
22
|
const { newSkyrampPlaywrightPage, expect } = require('./classes/SmartPlaywright');
|
|
23
23
|
const {
|
|
24
24
|
workspaceConfigSchema,
|
|
@@ -54,9 +54,11 @@ module.exports = {
|
|
|
54
54
|
getValue,
|
|
55
55
|
getResponseValue,
|
|
56
56
|
checkStatusCode,
|
|
57
|
+
checkRequestPayload,
|
|
57
58
|
checkSchema,
|
|
58
59
|
iterate,
|
|
59
60
|
pushToolEvent,
|
|
61
|
+
getBaseUrl,
|
|
60
62
|
newSkyrampPlaywrightPage,
|
|
61
63
|
expect,
|
|
62
64
|
workspaceConfigSchema,
|
package/src/utils.d.ts
CHANGED
|
@@ -104,3 +104,11 @@ export function checkForUpdate(component: string): Promise<void>;
|
|
|
104
104
|
* The Skyramp YAML version constant.
|
|
105
105
|
*/
|
|
106
106
|
export const SKYRAMP_YAML_VERSION: string;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Returns the base URL from an environment variable, with a fallback default.
|
|
110
|
+
* @param envVar - The environment variable name
|
|
111
|
+
* @param defaultUrl - The default URL if the env var is not set
|
|
112
|
+
* @returns The resolved base URL
|
|
113
|
+
*/
|
|
114
|
+
export function getBaseUrl(envVar: string, defaultUrl: string): string;
|
package/src/utils.js
CHANGED
|
@@ -257,6 +257,10 @@ function checkForUpdate(component) {
|
|
|
257
257
|
});
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
function getBaseUrl(envVar, defaultUrl) {
|
|
261
|
+
return process.env[envVar] ?? defaultUrl;
|
|
262
|
+
}
|
|
263
|
+
|
|
260
264
|
module.exports = {
|
|
261
265
|
createTestDescriptionFromScenario,
|
|
262
266
|
getYamlBytes,
|
|
@@ -267,5 +271,6 @@ module.exports = {
|
|
|
267
271
|
iterate,
|
|
268
272
|
pushToolEvent,
|
|
269
273
|
checkForUpdate,
|
|
274
|
+
getBaseUrl,
|
|
270
275
|
SKYRAMP_YAML_VERSION
|
|
271
276
|
}
|