artes 1.0.78 → 1.0.80
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 +43 -44
- package/cucumber.config.js +12 -3
- package/docs/functionDefinitions.md +102 -44
- package/executer.js +2 -1
- package/index.js +1 -1
- package/package.json +1 -1
- package/src/helper/contextManager/browserManager.js +3 -3
- package/src/helper/executers/helper.js +5 -2
- package/src/helper/executers/testRunner.js +5 -2
- package/src/helper/pomController/elementController.js +0 -1
- package/src/helper/stepFunctions/APIActions.js +17 -9
- package/src/helper/stepFunctions/browserActions.js +1 -1
- package/src/hooks/hooks.js +57 -30
- package/src/stepDefinitions/API.steps.js +5 -1
- package/src/stepDefinitions/random.steps.js +7 -7
package/README.md
CHANGED
|
@@ -42,21 +42,21 @@ npx artes [options]
|
|
|
42
42
|
|
|
43
43
|
### Options
|
|
44
44
|
|
|
45
|
-
| Option
|
|
46
|
-
|
|
|
47
|
-
| 🆘 `-h, --help`
|
|
48
|
-
| 🏷️ `-v, --version`
|
|
49
|
-
| 🏗️ `-c, --create`
|
|
50
|
-
| ✅ `-y, --yes`
|
|
51
|
-
| 📊 `-r, --report`
|
|
52
|
-
| 📁 `--features`
|
|
53
|
-
| 🔖 `--tags`
|
|
54
|
-
| 🌐 `--env`
|
|
55
|
-
| 🕶️ `--headless`
|
|
56
|
-
| ⚡ `--parallel`
|
|
57
|
-
| 🔁 `--retry`
|
|
58
|
-
| 🎭 `--dryrun`
|
|
59
|
-
|
|
45
|
+
| Option | Description | Usage Example |
|
|
46
|
+
| ------------------ | -------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
47
|
+
| 🆘 `-h, --help` | Show the usage options | `artes -h` or `artes --help` |
|
|
48
|
+
| 🏷️ `-v, --version` | Show the current version of Artes | `artes -v` or `artes --version` |
|
|
49
|
+
| 🏗️ `-c, --create` | Create an example project with Artes | `artes -c` or `artes --create` |
|
|
50
|
+
| ✅ `-y, --yes` | Skip the confirmation prompt when creating an example project | `artes -c -y` or `artes --create --yes` |
|
|
51
|
+
| 📊 `-r, --report` | Run tests and generate Allure report | `artes -r` or `artes --report` |
|
|
52
|
+
| 📁 `--features` | Specify one or more feature files' relative paths to run (comma-separated) | `artes --features "tests/features/Alma, tests/features/Banan.feature"` |
|
|
53
|
+
| 🔖 `--tags` | Run tests with specified Cucumber tags | `artes --tags "@smoke or @wip"` |
|
|
54
|
+
| 🌐 `--env` | Set the environment for the test run | `artes --env "dev"` |
|
|
55
|
+
| 🕶️ `--headless` | Run browser in headless mode | `artes --headless` |
|
|
56
|
+
| ⚡ `--parallel` | Run tests in parallel mode | `artes --parallel 2` |
|
|
57
|
+
| 🔁 `--retry` | Retry failed tests | `artes --retry 3` |
|
|
58
|
+
| 🎭 `--dryrun` | Perform a dry run without executing tests | `artes --dryrun` |
|
|
59
|
+
| 📈 `--percentage` | Set minimum success percentage to pass test run(default is 0) | `artes --percentage 85` |
|
|
60
60
|
|
|
61
61
|
\*\* To just run the tests: <br>
|
|
62
62
|
Globally: artes <br>
|
|
@@ -277,39 +277,38 @@ Then("User should see the login form", async () => {
|
|
|
277
277
|
|
|
278
278
|
You can configure Artes by editing the `artes.config.js` file. Below are the default configuration options with explanations:
|
|
279
279
|
|
|
280
|
-
|
|
281
|
-
|
|
|
282
|
-
|
|
|
283
|
-
| `
|
|
284
|
-
| `paths` | `[moduleConfig.featuresPath]` | Paths to feature files. |
|
|
280
|
+
| **Option** | **Default Value** | **Description** |
|
|
281
|
+
| ----------------- | ---------------------------------------------------------------------------- | ----------------------------------- |
|
|
282
|
+
| `timeout` | `30` | Default timeout in milliseconds. |
|
|
283
|
+
| `paths` | `[moduleConfig.featuresPath]` | Paths to feature files. |
|
|
285
284
|
| `require` | `[moduleConfig.stepsPath, "src/stepDefinitions/*.js", "src/hooks/hooks.js"]` | Support code paths (CommonJS). |
|
|
286
|
-
| `pomPath` | `moduleConfig.pomPath`
|
|
287
|
-
| `import` | `[]`
|
|
288
|
-
| `format` | `["rerun:@rerun.txt", "allure-cucumberjs/reporter"]`
|
|
289
|
-
| `formatOptions` | `{ "resultsDir": "allure-result" }`
|
|
290
|
-
| `parallel` | `1`
|
|
291
|
-
| `dryRun` | `false`
|
|
292
|
-
| `failFast` | `false`
|
|
293
|
-
| `forceExit` | `false`
|
|
294
|
-
| `strict` | `true`
|
|
295
|
-
| `backtrace` | `false`
|
|
296
|
-
| `tags` | `""`
|
|
297
|
-
| `name` | `[]`
|
|
298
|
-
| `order` | `"defined"`
|
|
299
|
-
| `language` | `"en"`
|
|
300
|
-
| `loader` | `[]`
|
|
301
|
-
| `requireModule` | `[]`
|
|
302
|
-
| `retry` | `0`
|
|
303
|
-
| `retryTagFilter` | `""`
|
|
304
|
-
| `publish` | `false`
|
|
305
|
-
| `worldParameters` | `{}`
|
|
285
|
+
| `pomPath` | `moduleConfig.pomPath` | Path to Page Object Models. |
|
|
286
|
+
| `import` | `[]` | Support code paths. |
|
|
287
|
+
| `format` | `["rerun:@rerun.txt", "allure-cucumberjs/reporter"]` | Formatter names/paths. |
|
|
288
|
+
| `formatOptions` | `{ "resultsDir": "allure-result" }` | Formatter options. |
|
|
289
|
+
| `parallel` | `1` | Number of parallel workers. |
|
|
290
|
+
| `dryRun` | `false` | Prepare test run without execution. |
|
|
291
|
+
| `failFast` | `false` | Stop on first test failure. |
|
|
292
|
+
| `forceExit` | `false` | Force `process.exit()` after tests. |
|
|
293
|
+
| `strict` | `true` | Fail on pending steps. |
|
|
294
|
+
| `backtrace` | `false` | Show full backtrace for errors. |
|
|
295
|
+
| `tags` | `""` | Tag expression to filter scenarios. |
|
|
296
|
+
| `name` | `[]` | Run scenarios matching regex. |
|
|
297
|
+
| `order` | `"defined"` | Run order (defined/random). |
|
|
298
|
+
| `language` | `"en"` | Default feature file language. |
|
|
299
|
+
| `loader` | `[]` | Module loader specifications. |
|
|
300
|
+
| `requireModule` | `[]` | Transpilation module names. |
|
|
301
|
+
| `retry` | `0` | Retry attempts for failing tests. |
|
|
302
|
+
| `retryTagFilter` | `""` | Tag expression for retries. |
|
|
303
|
+
| `publish` | `false` | Publish to cucumber.io. |
|
|
304
|
+
| `worldParameters` | `{}` | Custom world parameters. |
|
|
306
305
|
|
|
307
306
|
### Environment Configuration
|
|
308
307
|
|
|
309
|
-
| **Option**
|
|
310
|
-
|
|
|
311
|
-
| `env`
|
|
312
|
-
| `baseURL`
|
|
308
|
+
| **Option** | **Default Value** | **Description** |
|
|
309
|
+
| ---------- | ----------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
310
|
+
| `env` | `""` | Environment configuration. Should match the name with the baseURL object, like "dev" |
|
|
311
|
+
| `baseURL` | `""` | Base URL for API requests. Can be object {"dev":"dev-api.com", "pre":"pre-api.com"}, or string "dev-api.com" |
|
|
313
312
|
|
|
314
313
|
---
|
|
315
314
|
|
package/cucumber.config.js
CHANGED
|
@@ -17,6 +17,9 @@ try {
|
|
|
17
17
|
module.exports = {
|
|
18
18
|
default: {
|
|
19
19
|
// File paths and patterns
|
|
20
|
+
testPercentage: process.env.PERCENTAGE
|
|
21
|
+
? process.env.PERCENTAGE
|
|
22
|
+
: artesConfig.testPercentage || 0, // number - Percentage of tests to run (0-100)
|
|
20
23
|
timeout: artesConfig.timeout || 30, // Default timeout in milliseconds
|
|
21
24
|
paths: process.env.FEATURES
|
|
22
25
|
? [path.join(moduleConfig.projectPath, process.env.FEATURES)]
|
|
@@ -47,8 +50,12 @@ module.exports = {
|
|
|
47
50
|
}, // Formatter options
|
|
48
51
|
|
|
49
52
|
// Execution options
|
|
50
|
-
parallel: process.env.PARALLEL
|
|
51
|
-
|
|
53
|
+
parallel: process.env.PARALLEL
|
|
54
|
+
? JSON.parse(process.env.PARALLEL)
|
|
55
|
+
: artesConfig.parallel || 1, // Number of parallel workers
|
|
56
|
+
dryRun: process.env.DRYRUN
|
|
57
|
+
? JSON.parse(process.env.DRYRUN)
|
|
58
|
+
: artesConfig.dryRun || false, // Prepare test run without execution
|
|
52
59
|
failFast: artesConfig.failFast || false, // Stop on first test failure
|
|
53
60
|
forceExit: artesConfig.forceExit || false, // Force process.exit() after tests
|
|
54
61
|
strict: artesConfig.strict || true, // Fail on pending steps
|
|
@@ -67,7 +74,9 @@ module.exports = {
|
|
|
67
74
|
requireModule: artesConfig.requireModule || [], // Transpilation module names
|
|
68
75
|
|
|
69
76
|
// Retry logic
|
|
70
|
-
retry: process.env.RETRY
|
|
77
|
+
retry: process.env.RETRY
|
|
78
|
+
? JSON.parse(process.env.RETRY)
|
|
79
|
+
: artesConfig.retry || 0, // Retry attempts for failing tests
|
|
71
80
|
retryTagFilter: artesConfig.retryTagFilter || "", // Tag expression for retries
|
|
72
81
|
|
|
73
82
|
// Publishing
|
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
If you don't want to deal with Playwright methods directly, you can simply use the following predefined actions methods by import them:
|
|
4
4
|
|
|
5
5
|
```javascript
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
mouse,
|
|
8
|
+
keyboard,
|
|
9
|
+
frame,
|
|
10
|
+
elementInteractions,
|
|
11
|
+
page,
|
|
12
|
+
api,
|
|
13
|
+
} = require("artes");
|
|
7
14
|
```
|
|
8
15
|
|
|
9
16
|
- **Mouse Actions:**
|
|
@@ -2114,16 +2121,21 @@ The `api` object provides methods for making HTTP requests with automatic URL re
|
|
|
2114
2121
|
Makes a GET request to the specified URL.
|
|
2115
2122
|
|
|
2116
2123
|
**Parameters:**
|
|
2124
|
+
|
|
2117
2125
|
- `url` (string): The target URL (supports variable resolution)
|
|
2118
2126
|
- `payload` (string, optional): JSON string containing headers and other request options
|
|
2119
2127
|
|
|
2120
2128
|
**Returns:** Promise that resolves when the request completes
|
|
2121
2129
|
|
|
2122
2130
|
**Example:**
|
|
2131
|
+
|
|
2123
2132
|
```javascript
|
|
2124
|
-
await api.get(
|
|
2125
|
-
|
|
2126
|
-
|
|
2133
|
+
await api.get(
|
|
2134
|
+
"https://api.example.com/users",
|
|
2135
|
+
JSON.stringify({
|
|
2136
|
+
headers: { Authorization: "Bearer {{token}}" },
|
|
2137
|
+
}),
|
|
2138
|
+
);
|
|
2127
2139
|
```
|
|
2128
2140
|
|
|
2129
2141
|
### `api.head(url)`
|
|
@@ -2131,11 +2143,13 @@ await api.get("https://api.example.com/users", JSON.stringify({
|
|
|
2131
2143
|
Makes a HEAD request to the specified URL.
|
|
2132
2144
|
|
|
2133
2145
|
**Parameters:**
|
|
2146
|
+
|
|
2134
2147
|
- `url` (string): The target URL (supports variable resolution)
|
|
2135
2148
|
|
|
2136
2149
|
**Returns:** Promise that resolves when the request completes
|
|
2137
2150
|
|
|
2138
2151
|
**Example:**
|
|
2152
|
+
|
|
2139
2153
|
```javascript
|
|
2140
2154
|
await api.head("https://api.example.com/status");
|
|
2141
2155
|
```
|
|
@@ -2145,6 +2159,7 @@ await api.head("https://api.example.com/status");
|
|
|
2145
2159
|
Makes a POST request to the specified URL.
|
|
2146
2160
|
|
|
2147
2161
|
**Parameters:**
|
|
2162
|
+
|
|
2148
2163
|
- `url` (string): The target URL (supports variable resolution)
|
|
2149
2164
|
- `payload` (string): JSON string containing headers, body, and other request options
|
|
2150
2165
|
- `requestDataType` (string, optional): Request data type ("multipart" for form data, default for JSON)
|
|
@@ -2152,18 +2167,26 @@ Makes a POST request to the specified URL.
|
|
|
2152
2167
|
**Returns:** Promise that resolves when the request completes
|
|
2153
2168
|
|
|
2154
2169
|
**Example:**
|
|
2170
|
+
|
|
2155
2171
|
```javascript
|
|
2156
2172
|
// Regular POST request
|
|
2157
|
-
await api.post(
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
}
|
|
2173
|
+
await api.post(
|
|
2174
|
+
"https://api.example.com/users",
|
|
2175
|
+
JSON.stringify({
|
|
2176
|
+
headers: { "Content-Type": "application/json" },
|
|
2177
|
+
body: { name: "John", email: "john@example.com" },
|
|
2178
|
+
}),
|
|
2179
|
+
);
|
|
2161
2180
|
|
|
2162
2181
|
// Multipart form data
|
|
2163
|
-
await api.post(
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
}
|
|
2182
|
+
await api.post(
|
|
2183
|
+
"https://api.example.com/upload",
|
|
2184
|
+
JSON.stringify({
|
|
2185
|
+
headers: {},
|
|
2186
|
+
body: { file: "/path/to/file.pdf", description: "Document upload" },
|
|
2187
|
+
}),
|
|
2188
|
+
"multipart",
|
|
2189
|
+
);
|
|
2167
2190
|
```
|
|
2168
2191
|
|
|
2169
2192
|
### `api.put(url, payload, requestDataType)`
|
|
@@ -2171,6 +2194,7 @@ await api.post("https://api.example.com/upload", JSON.stringify({
|
|
|
2171
2194
|
Makes a PUT request to the specified URL.
|
|
2172
2195
|
|
|
2173
2196
|
**Parameters:**
|
|
2197
|
+
|
|
2174
2198
|
- `url` (string): The target URL (supports variable resolution)
|
|
2175
2199
|
- `payload` (string): JSON string containing headers, body, and other request options
|
|
2176
2200
|
- `requestDataType` (string, optional): Request data type ("multipart" for form data, default for JSON)
|
|
@@ -2178,11 +2202,15 @@ Makes a PUT request to the specified URL.
|
|
|
2178
2202
|
**Returns:** Promise that resolves when the request completes
|
|
2179
2203
|
|
|
2180
2204
|
**Example:**
|
|
2205
|
+
|
|
2181
2206
|
```javascript
|
|
2182
|
-
await api.put(
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
}
|
|
2207
|
+
await api.put(
|
|
2208
|
+
"https://api.example.com/users/{{userId}}",
|
|
2209
|
+
JSON.stringify({
|
|
2210
|
+
headers: { "Content-Type": "application/json" },
|
|
2211
|
+
body: { name: "Updated Name" },
|
|
2212
|
+
}),
|
|
2213
|
+
);
|
|
2186
2214
|
```
|
|
2187
2215
|
|
|
2188
2216
|
### `api.patch(url, payload, requestDataType)`
|
|
@@ -2190,6 +2218,7 @@ await api.put("https://api.example.com/users/{{userId}}", JSON.stringify({
|
|
|
2190
2218
|
Makes a PATCH request to the specified URL.
|
|
2191
2219
|
|
|
2192
2220
|
**Parameters:**
|
|
2221
|
+
|
|
2193
2222
|
- `url` (string): The target URL (supports variable resolution)
|
|
2194
2223
|
- `payload` (string): JSON string containing headers, body, and other request options
|
|
2195
2224
|
- `requestDataType` (string, optional): Request data type ("multipart" for form data, default for JSON)
|
|
@@ -2197,11 +2226,15 @@ Makes a PATCH request to the specified URL.
|
|
|
2197
2226
|
**Returns:** Promise that resolves when the request completes
|
|
2198
2227
|
|
|
2199
2228
|
**Example:**
|
|
2229
|
+
|
|
2200
2230
|
```javascript
|
|
2201
|
-
await api.patch(
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
}
|
|
2231
|
+
await api.patch(
|
|
2232
|
+
"https://api.example.com/users/{{userId}}",
|
|
2233
|
+
JSON.stringify({
|
|
2234
|
+
headers: { "Content-Type": "application/json" },
|
|
2235
|
+
body: { email: "newemail@example.com" },
|
|
2236
|
+
}),
|
|
2237
|
+
);
|
|
2205
2238
|
```
|
|
2206
2239
|
|
|
2207
2240
|
### `api.delete(url, payload)`
|
|
@@ -2209,16 +2242,21 @@ await api.patch("https://api.example.com/users/{{userId}}", JSON.stringify({
|
|
|
2209
2242
|
Makes a DELETE request to the specified URL.
|
|
2210
2243
|
|
|
2211
2244
|
**Parameters:**
|
|
2245
|
+
|
|
2212
2246
|
- `url` (string): The target URL (supports variable resolution)
|
|
2213
2247
|
- `payload` (string, optional): JSON string containing headers and other request options
|
|
2214
2248
|
|
|
2215
2249
|
**Returns:** Promise that resolves when the request completes
|
|
2216
2250
|
|
|
2217
2251
|
**Example:**
|
|
2252
|
+
|
|
2218
2253
|
```javascript
|
|
2219
|
-
await api.delete(
|
|
2220
|
-
|
|
2221
|
-
|
|
2254
|
+
await api.delete(
|
|
2255
|
+
"https://api.example.com/users/{{userId}}",
|
|
2256
|
+
JSON.stringify({
|
|
2257
|
+
headers: { Authorization: "Bearer {{token}}" },
|
|
2258
|
+
}),
|
|
2259
|
+
);
|
|
2222
2260
|
```
|
|
2223
2261
|
|
|
2224
2262
|
### `api.vars()`
|
|
@@ -2228,12 +2266,12 @@ Returns the current context variables object.
|
|
|
2228
2266
|
**Returns:** Object containing all stored variables
|
|
2229
2267
|
|
|
2230
2268
|
**Example:**
|
|
2269
|
+
|
|
2231
2270
|
```javascript
|
|
2232
2271
|
const currentVars = api.vars();
|
|
2233
2272
|
console.log(currentVars); // { token: "abc123", userId: "user456" }
|
|
2234
2273
|
```
|
|
2235
2274
|
|
|
2236
|
-
|
|
2237
2275
|
## Static Methods
|
|
2238
2276
|
|
|
2239
2277
|
### `extractVarsFromResponse(object, vars, customVarName)`
|
|
@@ -2241,15 +2279,18 @@ console.log(currentVars); // { token: "abc123", userId: "user456" }
|
|
|
2241
2279
|
Extracts variables from the response body using dot notation paths.
|
|
2242
2280
|
|
|
2243
2281
|
**Parameters:**
|
|
2282
|
+
|
|
2244
2283
|
- `vars` (string): Comma-separated list of dot notation paths (e.g., "user.id,user.name")
|
|
2245
2284
|
- `customVarName` (string, optional): Custom variable name to use instead of auto-generated names
|
|
2246
2285
|
|
|
2247
2286
|
**Behavior:**
|
|
2287
|
+
|
|
2248
2288
|
- Extracts values from `context.response.responseBody` using specified paths
|
|
2249
2289
|
- Saves extracted values as variables using `saveVar()`
|
|
2250
2290
|
- If `customVarName` is provided, uses it; otherwise generates camelCase names
|
|
2251
2291
|
|
|
2252
2292
|
**Example:**
|
|
2293
|
+
|
|
2253
2294
|
```javascript
|
|
2254
2295
|
// Response body: { user: { id: 123, profile: { name: "John" } } }
|
|
2255
2296
|
extractVarsFromResponse(object, "user.id,user.profile.name");
|
|
@@ -2264,11 +2305,13 @@ extractVarsFromResponse(object, "user.id", "currentUserId");
|
|
|
2264
2305
|
Saves a variable to the context with either a custom name or auto-generated camelCase name.
|
|
2265
2306
|
|
|
2266
2307
|
**Parameters:**
|
|
2308
|
+
|
|
2267
2309
|
- `value` (any): The value to store
|
|
2268
2310
|
- `customName` (string, optional): Custom variable name
|
|
2269
2311
|
- `path` (string): Dot notation path used for auto-generating variable name
|
|
2270
2312
|
|
|
2271
2313
|
**Behavior:**
|
|
2314
|
+
|
|
2272
2315
|
- If `customName` is provided, uses it as the variable name
|
|
2273
2316
|
- Otherwise, converts dot notation path to camelCase (e.g., "user.profile.name" → "userProfileName")
|
|
2274
2317
|
|
|
@@ -2277,17 +2320,19 @@ Saves a variable to the context with either a custom name or auto-generated came
|
|
|
2277
2320
|
Resolves variable placeholders in template strings using the format `{{variableName}}`.
|
|
2278
2321
|
|
|
2279
2322
|
**Parameters:**
|
|
2323
|
+
|
|
2280
2324
|
- `template` (string): Template string containing variable placeholders
|
|
2281
2325
|
|
|
2282
2326
|
**Returns:** String with variables resolved, or original value if not a string
|
|
2283
2327
|
|
|
2284
2328
|
**Example:**
|
|
2329
|
+
|
|
2285
2330
|
```javascript
|
|
2286
2331
|
// Assuming context.vars = { userId: "123", token: "abc" }
|
|
2287
|
-
resolveVariable("https://api.example.com/users/{{userId}}")
|
|
2332
|
+
resolveVariable("https://api.example.com/users/{{userId}}");
|
|
2288
2333
|
// Returns: "https://api.example.com/users/123"
|
|
2289
2334
|
|
|
2290
|
-
resolveVariable("Bearer {{token}}")
|
|
2335
|
+
resolveVariable("Bearer {{token}}");
|
|
2291
2336
|
// Returns: "Bearer abc"
|
|
2292
2337
|
```
|
|
2293
2338
|
|
|
@@ -2301,9 +2346,12 @@ context.vars.baseUrl = "https://api.example.com";
|
|
|
2301
2346
|
context.vars.token = "your-auth-token";
|
|
2302
2347
|
|
|
2303
2348
|
// Make a GET request
|
|
2304
|
-
await api.get(
|
|
2305
|
-
|
|
2306
|
-
|
|
2349
|
+
await api.get(
|
|
2350
|
+
"{{baseUrl}}/users",
|
|
2351
|
+
JSON.stringify({
|
|
2352
|
+
headers: { Authorization: "Bearer {{token}}" },
|
|
2353
|
+
}),
|
|
2354
|
+
);
|
|
2307
2355
|
|
|
2308
2356
|
// Extract user ID from response
|
|
2309
2357
|
extractVarsFromResponse("data.0.id", "firstUserId");
|
|
@@ -2315,29 +2363,39 @@ await api.get("{{baseUrl}}/users/{{firstUserId}}/profile");
|
|
|
2315
2363
|
### File Upload Example
|
|
2316
2364
|
|
|
2317
2365
|
```javascript
|
|
2318
|
-
await api.post(
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2366
|
+
await api.post(
|
|
2367
|
+
"{{baseUrl}}/upload",
|
|
2368
|
+
JSON.stringify({
|
|
2369
|
+
headers: { Authorization: "Bearer {{token}}" },
|
|
2370
|
+
body: {
|
|
2371
|
+
file: "/path/to/document.pdf",
|
|
2372
|
+
description: "Important document",
|
|
2373
|
+
metadata: { type: "legal", priority: "high" },
|
|
2374
|
+
},
|
|
2375
|
+
}),
|
|
2376
|
+
"multipart",
|
|
2377
|
+
);
|
|
2326
2378
|
```
|
|
2327
2379
|
|
|
2328
2380
|
### Variable Extraction and Chaining
|
|
2329
2381
|
|
|
2330
2382
|
```javascript
|
|
2331
2383
|
// Login request
|
|
2332
|
-
await api.post(
|
|
2333
|
-
|
|
2334
|
-
|
|
2384
|
+
await api.post(
|
|
2385
|
+
"{{baseUrl}}/auth/login",
|
|
2386
|
+
JSON.stringify({
|
|
2387
|
+
body: { username: "user@example.com", password: "password" },
|
|
2388
|
+
}),
|
|
2389
|
+
);
|
|
2335
2390
|
|
|
2336
2391
|
// Extract token from login response
|
|
2337
2392
|
extractVarsFromResponse("access_token", "authToken");
|
|
2338
2393
|
|
|
2339
2394
|
// Use token in protected endpoint
|
|
2340
|
-
await api.get(
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2395
|
+
await api.get(
|
|
2396
|
+
"{{baseUrl}}/protected-data",
|
|
2397
|
+
JSON.stringify({
|
|
2398
|
+
headers: { Authorization: "Bearer {{authToken}}" },
|
|
2399
|
+
}),
|
|
2400
|
+
);
|
|
2401
|
+
```
|
package/executer.js
CHANGED
|
@@ -24,7 +24,8 @@ const flags = {
|
|
|
24
24
|
headless: args.includes("--headless"),
|
|
25
25
|
parallel: args.includes("--parallel"),
|
|
26
26
|
retry: args.includes("--retry"),
|
|
27
|
-
dryrun: args.includes("--dryrun")
|
|
27
|
+
dryrun: args.includes("--dryrun"),
|
|
28
|
+
percentage: args.includes("--percentage"),
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
function main() {
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -58,13 +58,13 @@ const invokeBrowser = async () => {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
const context = await browser.newContext(browserContextOptions);
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
return {
|
|
63
63
|
browser: browser,
|
|
64
|
-
context: context
|
|
64
|
+
context: context,
|
|
65
65
|
};
|
|
66
66
|
};
|
|
67
67
|
|
|
68
68
|
module.exports = {
|
|
69
69
|
invokeBrowser,
|
|
70
|
-
};
|
|
70
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
function showHelp() {
|
|
2
|
-
|
|
2
|
+
console.log(`
|
|
3
3
|
🚀 Artes - Playwright Test Runner
|
|
4
4
|
|
|
5
5
|
Description:
|
|
@@ -45,8 +45,11 @@ function showHelp() {
|
|
|
45
45
|
|
|
46
46
|
🎭 --dryrun Perform a dry run without executing tests
|
|
47
47
|
Usage: artes --dryrun
|
|
48
|
+
|
|
49
|
+
📈 --percentage Set minimum success percentage to pass test run
|
|
50
|
+
Usage: artes --percentage 85
|
|
48
51
|
`);
|
|
49
|
-
|
|
52
|
+
}
|
|
50
53
|
|
|
51
54
|
module.exports = {
|
|
52
55
|
showHelp,
|
|
@@ -11,14 +11,16 @@ function runTests(args, flags) {
|
|
|
11
11
|
const tags = args[args.indexOf("--tags") + 1];
|
|
12
12
|
const parallel = args[args.indexOf("--parallel") + 1];
|
|
13
13
|
const retry = args[args.indexOf("--retry") + 1];
|
|
14
|
+
const percentage = args[args.indexOf("percentage") + 1];
|
|
14
15
|
|
|
15
16
|
flags.env && console.log("Running env:", env);
|
|
16
17
|
flags.env ? (process.env.ENV = JSON.stringify(env)) : "";
|
|
17
18
|
|
|
18
19
|
flags.report
|
|
19
20
|
? (process.env.REPORT_FORMAT = JSON.stringify([
|
|
20
|
-
"rerun:@rerun.txt",
|
|
21
|
-
"
|
|
21
|
+
"rerun:@rerun.txt",
|
|
22
|
+
"progress-bar",
|
|
23
|
+
"allure-cucumberjs/reporter:./allure-results",
|
|
22
24
|
]))
|
|
23
25
|
: "";
|
|
24
26
|
|
|
@@ -35,6 +37,7 @@ function runTests(args, flags) {
|
|
|
35
37
|
flags.parallel ? (process.env.PARALLEL = JSON.stringify(parallel)) : "";
|
|
36
38
|
flags.retry ? (process.env.RETRY = JSON.stringify(retry)) : "";
|
|
37
39
|
flags.dryrun ? (process.env.DRYRUN = JSON.stringify(true)) : "";
|
|
40
|
+
flags.percentage ? (process.env.PERCENTAGE = percentage) : "";
|
|
38
41
|
|
|
39
42
|
try {
|
|
40
43
|
console.log("🧪 Running tests...");
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
2
|
const fs = require("fs");
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
context,
|
|
5
|
+
selector,
|
|
6
|
+
resolveVariable,
|
|
7
|
+
moduleConfig,
|
|
8
|
+
} = require("../imports/commons");
|
|
4
9
|
|
|
5
10
|
function getMimeType(filePath) {
|
|
6
11
|
const ext = path.extname(filePath).toLowerCase();
|
|
@@ -90,11 +95,12 @@ async function requestMaker(headers, data, requestDataType) {
|
|
|
90
95
|
async function responseMaker(request, response, duration) {
|
|
91
96
|
const responseObject = {};
|
|
92
97
|
|
|
93
|
-
response &&
|
|
94
|
-
|
|
98
|
+
response &&
|
|
99
|
+
Object.assign(responseObject, {
|
|
100
|
+
"Response Params": `URL: ${response.url()}
|
|
95
101
|
Response Status: ${await response.status()}
|
|
96
|
-
Response Time: ${Math.round(duration)} ms
|
|
97
|
-
|
|
102
|
+
Response Time: ${Math.round(duration)} ms`,
|
|
103
|
+
});
|
|
98
104
|
|
|
99
105
|
request?.headers &&
|
|
100
106
|
Object.assign(responseObject, { "Request Headers": await request.headers });
|
|
@@ -117,7 +123,9 @@ async function responseMaker(request, response, duration) {
|
|
|
117
123
|
}
|
|
118
124
|
}
|
|
119
125
|
|
|
120
|
-
Object.assign(responseObject, {
|
|
126
|
+
Object.assign(responseObject, {
|
|
127
|
+
"Response Time": `${Math.round(duration)} ms`,
|
|
128
|
+
});
|
|
121
129
|
|
|
122
130
|
return responseObject;
|
|
123
131
|
}
|
|
@@ -168,7 +176,7 @@ const api = {
|
|
|
168
176
|
switch (requestDataType) {
|
|
169
177
|
case "multipart":
|
|
170
178
|
let formData = {};
|
|
171
|
-
|
|
179
|
+
|
|
172
180
|
for (const [key, value] of Object.entries(payloadJSON?.body)) {
|
|
173
181
|
processForm(formData, key, value);
|
|
174
182
|
}
|
|
@@ -208,7 +216,7 @@ const api = {
|
|
|
208
216
|
switch (requestDataType) {
|
|
209
217
|
case "multipart":
|
|
210
218
|
let formData = {};
|
|
211
|
-
|
|
219
|
+
|
|
212
220
|
for (const [key, value] of Object.entries(payloadJSON?.body)) {
|
|
213
221
|
processForm(formData, key, value);
|
|
214
222
|
}
|
|
@@ -249,7 +257,7 @@ const api = {
|
|
|
249
257
|
switch (requestDataType) {
|
|
250
258
|
case "multipart":
|
|
251
259
|
let formData = {};
|
|
252
|
-
|
|
260
|
+
|
|
253
261
|
for (const [key, value] of Object.entries(payloadJSON?.body)) {
|
|
254
262
|
processForm(formData, key, value);
|
|
255
263
|
}
|
package/src/hooks/hooks.js
CHANGED
|
@@ -3,8 +3,10 @@ const {
|
|
|
3
3
|
Before,
|
|
4
4
|
After,
|
|
5
5
|
Status,
|
|
6
|
-
setDefaultTimeout,
|
|
7
|
-
|
|
6
|
+
setDefaultTimeout,
|
|
7
|
+
AfterStep,
|
|
8
|
+
BeforeStep,
|
|
9
|
+
AfterAll,
|
|
8
10
|
} = require("@cucumber/cucumber");
|
|
9
11
|
const { invokeBrowser } = require("../helper/contextManager/browserManager");
|
|
10
12
|
const { invokeRequest } = require("../helper/contextManager/requestManager");
|
|
@@ -15,6 +17,9 @@ const fs = require("fs");
|
|
|
15
17
|
const { moduleConfig } = require("artes/src/helper/imports/commons");
|
|
16
18
|
const path = require("path");
|
|
17
19
|
|
|
20
|
+
let totalTests = 0;
|
|
21
|
+
let testsFailed = 0;
|
|
22
|
+
|
|
18
23
|
setDefaultTimeout(cucumberConfig.default.timeout * 1000);
|
|
19
24
|
|
|
20
25
|
BeforeAll(async function () {
|
|
@@ -22,6 +27,7 @@ BeforeAll(async function () {
|
|
|
22
27
|
});
|
|
23
28
|
|
|
24
29
|
Before(async function () {
|
|
30
|
+
totalTests++;
|
|
25
31
|
context.vars = {};
|
|
26
32
|
|
|
27
33
|
const { browser, context: browserContext } = await invokeBrowser();
|
|
@@ -44,36 +50,38 @@ Before(async function () {
|
|
|
44
50
|
BeforeStep(async function ({ pickleStep }) {
|
|
45
51
|
const stepText = pickleStep.text;
|
|
46
52
|
|
|
47
|
-
const methods = [
|
|
53
|
+
const methods = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"];
|
|
48
54
|
|
|
49
|
-
if(
|
|
50
|
-
|
|
55
|
+
if (methods.some((method) => stepText.includes(method))) {
|
|
56
|
+
context.response = {};
|
|
51
57
|
}
|
|
52
|
-
})
|
|
58
|
+
});
|
|
53
59
|
|
|
54
|
-
AfterStep(async function ({pickleStep}) {
|
|
60
|
+
AfterStep(async function ({ pickleStep }) {
|
|
55
61
|
const stepText = pickleStep.text;
|
|
56
62
|
|
|
57
|
-
const methods = [
|
|
63
|
+
const methods = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"];
|
|
58
64
|
|
|
59
|
-
if(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
if (methods.some((method) => stepText.includes(method))) {
|
|
66
|
+
if (await context.response) {
|
|
67
|
+
for (const [key, value] of Object.entries(context.response)) {
|
|
68
|
+
let text = `${key}:\n`;
|
|
69
|
+
|
|
70
|
+
if (typeof value === "object") {
|
|
71
|
+
text += JSON.stringify(value, null, 2);
|
|
72
|
+
} else {
|
|
73
|
+
text += value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await this.attach(text, "text/plain");
|
|
68
77
|
}
|
|
69
|
-
|
|
70
|
-
await this.attach(text, "text/plain");
|
|
71
78
|
}
|
|
72
|
-
}
|
|
73
|
-
})
|
|
79
|
+
}
|
|
80
|
+
});
|
|
74
81
|
|
|
75
82
|
After(async function ({ pickle, result }) {
|
|
76
83
|
if (result?.status != Status.PASSED) {
|
|
84
|
+
testsFailed++;
|
|
77
85
|
let img = await context.page.screenshot({
|
|
78
86
|
path: `./test-results/visualReport/${pickle.name}/${pickle.name}.png`,
|
|
79
87
|
type: "png",
|
|
@@ -88,33 +96,52 @@ After(async function ({ pickle, result }) {
|
|
|
88
96
|
if (context.response) {
|
|
89
97
|
for (const [key, value] of Object.entries(context.response)) {
|
|
90
98
|
let text = `${key}:\n`;
|
|
91
|
-
|
|
92
|
-
if (typeof value ===
|
|
99
|
+
|
|
100
|
+
if (typeof value === "object") {
|
|
93
101
|
text += JSON.stringify(value, null, 2);
|
|
94
102
|
} else {
|
|
95
103
|
text += value;
|
|
96
104
|
}
|
|
97
|
-
|
|
105
|
+
|
|
98
106
|
await this.attach(text, "text/plain");
|
|
99
107
|
}
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
await context.page?.close();
|
|
103
|
-
|
|
111
|
+
|
|
104
112
|
await context.browserContext?.close();
|
|
105
|
-
|
|
113
|
+
|
|
106
114
|
await context.browser?.close();
|
|
107
|
-
|
|
115
|
+
|
|
108
116
|
await context.request?.dispose();
|
|
109
117
|
|
|
110
118
|
if (result?.status != Status.PASSED && context.page.video()) {
|
|
111
119
|
const videoPath = await context.page.video().path();
|
|
112
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
113
|
-
|
|
120
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
121
|
+
|
|
114
122
|
if (fs.existsSync(videoPath)) {
|
|
115
123
|
const webmBuffer = await fs.readFileSync(videoPath);
|
|
116
124
|
await this.attach(webmBuffer, "video/webm");
|
|
117
125
|
}
|
|
118
126
|
}
|
|
127
|
+
});
|
|
119
128
|
|
|
120
|
-
|
|
129
|
+
AfterAll(async function () {
|
|
130
|
+
const successPercentage = 100 - (testsFailed / totalTests) * 100;
|
|
131
|
+
const successRate =
|
|
132
|
+
successPercentage.toFixed(2) >= cucumberConfig.testPercentage;
|
|
133
|
+
|
|
134
|
+
if (!isNaN(successPercentage)) {
|
|
135
|
+
if (successRate) {
|
|
136
|
+
console.log(
|
|
137
|
+
`Tests passed with ${successPercentage.toFixed(2)}% results!`,
|
|
138
|
+
);
|
|
139
|
+
process.exit(0);
|
|
140
|
+
} else {
|
|
141
|
+
console.log(
|
|
142
|
+
`Tests failed with ${successPercentage.toFixed(2)}% results!`,
|
|
143
|
+
);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
});
|
|
@@ -160,7 +160,11 @@ When(
|
|
|
160
160
|
When(
|
|
161
161
|
"User saves {string} variable from response as {string}",
|
|
162
162
|
async function (vars, customVarName) {
|
|
163
|
-
await extractVarsFromResponse(
|
|
163
|
+
await extractVarsFromResponse(
|
|
164
|
+
context.response["Response Body"],
|
|
165
|
+
vars,
|
|
166
|
+
customVarName,
|
|
167
|
+
);
|
|
164
168
|
},
|
|
165
169
|
);
|
|
166
170
|
|
|
@@ -18,14 +18,14 @@ Given(
|
|
|
18
18
|
"User sends GET request to {string} and save {string} variable from {string} array as a {string} randomly",
|
|
19
19
|
async (endPoint, varName, fromArray, variableKey) => {
|
|
20
20
|
await api.get(endPoint);
|
|
21
|
-
let responseBody
|
|
22
|
-
if(fromArray=="[]"){
|
|
23
|
-
|
|
24
|
-
}else{
|
|
25
|
-
|
|
21
|
+
let responseBody;
|
|
22
|
+
if (fromArray == "[]") {
|
|
23
|
+
responseBody = await context.response["Response Body"];
|
|
24
|
+
} else {
|
|
25
|
+
responseBody = await context.response["Response Body"][fromArray];
|
|
26
26
|
}
|
|
27
27
|
const randomContent =
|
|
28
|
-
|
|
28
|
+
responseBody[random.number.int({ min: 0, max: responseBody.length - 1 })];
|
|
29
29
|
context.vars[variableKey] = randomContent[varName];
|
|
30
30
|
},
|
|
31
|
-
);
|
|
31
|
+
);
|