codeceptjs 3.4.1 → 3.5.1-2.beta.7
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 +31 -30
- package/bin/codecept.js +1 -1
- package/lib/actor.js +6 -3
- package/lib/ai.js +180 -0
- package/lib/cli.js +13 -3
- package/lib/codecept.js +8 -0
- package/lib/colorUtils.js +10 -0
- package/lib/command/definitions.js +2 -7
- package/lib/command/dryRun.js +11 -2
- package/lib/command/generate.js +46 -3
- package/lib/command/info.js +24 -0
- package/lib/command/init.js +64 -6
- package/lib/command/interactive.js +15 -1
- package/lib/command/run-multiple/collection.js +17 -5
- package/lib/command/run-multiple.js +4 -2
- package/lib/command/run-workers.js +68 -5
- package/lib/command/run.js +7 -0
- package/lib/command/workers/runTests.js +39 -0
- package/lib/container.js +13 -3
- package/lib/data/context.js +14 -6
- package/lib/event.js +4 -0
- package/lib/helper/ApiDataFactory.js +2 -1
- package/lib/helper/Appium.js +116 -29
- package/lib/helper/Expect.js +422 -0
- package/lib/helper/FileSystem.js +1 -1
- package/lib/helper/GraphQL.js +25 -0
- package/lib/helper/JSONResponse.js +4 -4
- package/lib/helper/Nightmare.js +10 -5
- package/lib/helper/OpenAI.js +126 -0
- package/lib/helper/Playwright.js +1298 -229
- package/lib/helper/Protractor.js +12 -7
- package/lib/helper/Puppeteer.js +204 -64
- package/lib/helper/REST.js +15 -5
- package/lib/helper/TestCafe.js +45 -10
- package/lib/helper/WebDriver.js +252 -83
- package/lib/helper/errors/ElementNotFound.js +2 -1
- package/lib/helper/extras/PlaywrightReactVueLocator.js +38 -0
- package/lib/helper/scripts/blurElement.js +17 -0
- package/lib/helper/scripts/focusElement.js +17 -0
- package/lib/helper/scripts/highlightElement.js +20 -0
- package/lib/html.js +258 -0
- package/lib/interfaces/bdd.js +1 -1
- package/lib/interfaces/gherkin.js +37 -3
- package/lib/interfaces/scenarioConfig.js +1 -0
- package/lib/listener/retry.js +2 -1
- package/lib/locator.js +17 -4
- package/lib/mochaFactory.js +2 -1
- package/lib/output.js +1 -1
- package/lib/pause.js +78 -19
- package/lib/plugin/autoLogin.js +45 -10
- package/lib/plugin/debugErrors.js +67 -0
- package/lib/plugin/fakerTransform.js +4 -6
- package/lib/plugin/heal.js +209 -0
- package/lib/plugin/retryFailedStep.js +10 -1
- package/lib/plugin/retryTo.js +2 -4
- package/lib/plugin/screenshotOnFail.js +11 -2
- package/lib/plugin/selenoid.js +6 -1
- package/lib/plugin/standardActingHelpers.js +0 -2
- package/lib/plugin/stepByStepReport.js +2 -2
- package/lib/plugin/tryTo.js +5 -7
- package/lib/plugin/wdio.js +0 -1
- package/lib/recorder.js +22 -11
- package/lib/secret.js +5 -4
- package/lib/session.js +1 -1
- package/lib/step.js +36 -12
- package/lib/ui.js +5 -3
- package/lib/utils.js +22 -1
- package/lib/workers.js +83 -10
- package/package.json +117 -95
- package/translations/de-DE.js +5 -0
- package/translations/fr-FR.js +14 -1
- package/translations/it-IT.js +1 -0
- package/translations/ja-JP.js +14 -9
- package/translations/pl-PL.js +5 -0
- package/translations/pt-BR.js +1 -0
- package/translations/ru-RU.js +1 -0
- package/translations/zh-CN.js +5 -0
- package/translations/zh-TW.js +5 -0
- package/typings/index.d.ts +51 -15
- package/typings/promiseBasedTypes.d.ts +864 -802
- package/typings/types.d.ts +1339 -744
- package/CHANGELOG.md +0 -2427
- package/docs/advanced.md +0 -351
- package/docs/api.md +0 -323
- package/docs/basics.md +0 -980
- package/docs/bdd.md +0 -535
- package/docs/best.md +0 -237
- package/docs/books.md +0 -37
- package/docs/bootstrap.md +0 -135
- package/docs/build/ApiDataFactory.js +0 -409
- package/docs/build/Appium.js +0 -1938
- package/docs/build/FileSystem.js +0 -228
- package/docs/build/GraphQL.js +0 -204
- package/docs/build/GraphQLDataFactory.js +0 -309
- package/docs/build/JSONResponse.js +0 -338
- package/docs/build/Mochawesome.js +0 -71
- package/docs/build/Nightmare.js +0 -2145
- package/docs/build/Playwright.js +0 -3986
- package/docs/build/Polly.js +0 -42
- package/docs/build/Protractor.js +0 -2699
- package/docs/build/Puppeteer.js +0 -3710
- package/docs/build/REST.js +0 -334
- package/docs/build/SeleniumWebdriver.js +0 -76
- package/docs/build/TestCafe.js +0 -2057
- package/docs/build/WebDriver.js +0 -4017
- package/docs/changelog.md +0 -2436
- package/docs/commands.md +0 -254
- package/docs/community-helpers.md +0 -58
- package/docs/configuration.md +0 -157
- package/docs/continuous-integration.md +0 -22
- package/docs/custom-helpers.md +0 -306
- package/docs/data.md +0 -375
- package/docs/detox.md +0 -235
- package/docs/docker.md +0 -137
- package/docs/email.md +0 -183
- package/docs/examples.md +0 -149
- package/docs/helpers/ApiDataFactory.md +0 -266
- package/docs/helpers/Appium.md +0 -1312
- package/docs/helpers/Detox.md +0 -586
- package/docs/helpers/FileSystem.md +0 -152
- package/docs/helpers/GraphQL.md +0 -130
- package/docs/helpers/GraphQLDataFactory.md +0 -226
- package/docs/helpers/JSONResponse.md +0 -254
- package/docs/helpers/Mochawesome.md +0 -8
- package/docs/helpers/MockRequest.md +0 -377
- package/docs/helpers/Nightmare.md +0 -1256
- package/docs/helpers/Playwright.md +0 -2208
- package/docs/helpers/Polly.md +0 -44
- package/docs/helpers/Puppeteer-firefox.md +0 -86
- package/docs/helpers/Puppeteer.md +0 -2141
- package/docs/helpers/REST.md +0 -217
- package/docs/helpers/TestCafe.md +0 -1222
- package/docs/helpers/WebDriver.md +0 -2319
- package/docs/hooks.md +0 -340
- package/docs/index.md +0 -111
- package/docs/installation.md +0 -75
- package/docs/internal-api.md +0 -265
- package/docs/locators.md +0 -331
- package/docs/mobile-react-native-locators.md +0 -67
- package/docs/mobile.md +0 -297
- package/docs/nightmare.md +0 -223
- package/docs/pageobjects.md +0 -291
- package/docs/parallel.md +0 -232
- package/docs/playwright.md +0 -609
- package/docs/plugins.md +0 -1171
- package/docs/puppeteer.md +0 -316
- package/docs/quickstart.md +0 -163
- package/docs/react.md +0 -69
- package/docs/reports.md +0 -392
- package/docs/secrets.md +0 -30
- package/docs/shadow.md +0 -68
- package/docs/shared/keys.mustache +0 -31
- package/docs/shared/react.mustache +0 -1
- package/docs/testcafe.md +0 -174
- package/docs/translation.md +0 -247
- package/docs/tutorial.md +0 -271
- package/docs/typescript.md +0 -180
- package/docs/ui.md +0 -59
- package/docs/videos.md +0 -28
- package/docs/visual.md +0 -202
- package/docs/vue.md +0 -121
- package/docs/webapi/amOnPage.mustache +0 -11
- package/docs/webapi/appendField.mustache +0 -9
- package/docs/webapi/attachFile.mustache +0 -12
- package/docs/webapi/checkOption.mustache +0 -13
- package/docs/webapi/clearCookie.mustache +0 -10
- package/docs/webapi/clearField.mustache +0 -9
- package/docs/webapi/click.mustache +0 -25
- package/docs/webapi/clickLink.mustache +0 -8
- package/docs/webapi/closeCurrentTab.mustache +0 -7
- package/docs/webapi/closeOtherTabs.mustache +0 -8
- package/docs/webapi/dontSee.mustache +0 -11
- package/docs/webapi/dontSeeCheckboxIsChecked.mustache +0 -10
- package/docs/webapi/dontSeeCookie.mustache +0 -8
- package/docs/webapi/dontSeeCurrentUrlEquals.mustache +0 -10
- package/docs/webapi/dontSeeElement.mustache +0 -8
- package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
- package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
- package/docs/webapi/dontSeeInField.mustache +0 -11
- package/docs/webapi/dontSeeInSource.mustache +0 -8
- package/docs/webapi/dontSeeInTitle.mustache +0 -8
- package/docs/webapi/doubleClick.mustache +0 -13
- package/docs/webapi/downloadFile.mustache +0 -12
- package/docs/webapi/dragAndDrop.mustache +0 -9
- package/docs/webapi/dragSlider.mustache +0 -11
- package/docs/webapi/executeAsyncScript.mustache +0 -24
- package/docs/webapi/executeScript.mustache +0 -26
- package/docs/webapi/fillField.mustache +0 -16
- package/docs/webapi/forceClick.mustache +0 -28
- package/docs/webapi/forceRightClick.mustache +0 -18
- package/docs/webapi/grabAllWindowHandles.mustache +0 -7
- package/docs/webapi/grabAttributeFrom.mustache +0 -10
- package/docs/webapi/grabAttributeFromAll.mustache +0 -9
- package/docs/webapi/grabBrowserLogs.mustache +0 -9
- package/docs/webapi/grabCookie.mustache +0 -11
- package/docs/webapi/grabCssPropertyFrom.mustache +0 -11
- package/docs/webapi/grabCssPropertyFromAll.mustache +0 -10
- package/docs/webapi/grabCurrentUrl.mustache +0 -9
- package/docs/webapi/grabCurrentWindowHandle.mustache +0 -6
- package/docs/webapi/grabDataFromPerformanceTiming.mustache +0 -20
- package/docs/webapi/grabElementBoundingRect.mustache +0 -20
- package/docs/webapi/grabGeoLocation.mustache +0 -8
- package/docs/webapi/grabHTMLFrom.mustache +0 -10
- package/docs/webapi/grabHTMLFromAll.mustache +0 -9
- package/docs/webapi/grabNumberOfOpenTabs.mustache +0 -8
- package/docs/webapi/grabNumberOfVisibleElements.mustache +0 -9
- package/docs/webapi/grabPageScrollPosition.mustache +0 -8
- package/docs/webapi/grabPopupText.mustache +0 -5
- package/docs/webapi/grabSource.mustache +0 -8
- package/docs/webapi/grabTextFrom.mustache +0 -10
- package/docs/webapi/grabTextFromAll.mustache +0 -9
- package/docs/webapi/grabTitle.mustache +0 -8
- package/docs/webapi/grabValueFrom.mustache +0 -9
- package/docs/webapi/grabValueFromAll.mustache +0 -8
- package/docs/webapi/moveCursorTo.mustache +0 -12
- package/docs/webapi/openNewTab.mustache +0 -7
- package/docs/webapi/pressKey.mustache +0 -12
- package/docs/webapi/pressKeyDown.mustache +0 -12
- package/docs/webapi/pressKeyUp.mustache +0 -12
- package/docs/webapi/pressKeyWithKeyNormalization.mustache +0 -60
- package/docs/webapi/refreshPage.mustache +0 -6
- package/docs/webapi/resizeWindow.mustache +0 -6
- package/docs/webapi/rightClick.mustache +0 -14
- package/docs/webapi/saveElementScreenshot.mustache +0 -10
- package/docs/webapi/saveScreenshot.mustache +0 -12
- package/docs/webapi/say.mustache +0 -10
- package/docs/webapi/scrollIntoView.mustache +0 -11
- package/docs/webapi/scrollPageToBottom.mustache +0 -6
- package/docs/webapi/scrollPageToTop.mustache +0 -6
- package/docs/webapi/scrollTo.mustache +0 -12
- package/docs/webapi/see.mustache +0 -11
- package/docs/webapi/seeAttributesOnElements.mustache +0 -9
- package/docs/webapi/seeCheckboxIsChecked.mustache +0 -10
- package/docs/webapi/seeCookie.mustache +0 -8
- package/docs/webapi/seeCssPropertiesOnElements.mustache +0 -9
- package/docs/webapi/seeCurrentUrlEquals.mustache +0 -11
- package/docs/webapi/seeElement.mustache +0 -8
- package/docs/webapi/seeElementInDOM.mustache +0 -8
- package/docs/webapi/seeInCurrentUrl.mustache +0 -8
- package/docs/webapi/seeInField.mustache +0 -12
- package/docs/webapi/seeInPopup.mustache +0 -8
- package/docs/webapi/seeInSource.mustache +0 -7
- package/docs/webapi/seeInTitle.mustache +0 -8
- package/docs/webapi/seeNumberOfElements.mustache +0 -11
- package/docs/webapi/seeNumberOfVisibleElements.mustache +0 -10
- package/docs/webapi/seeTextEquals.mustache +0 -9
- package/docs/webapi/seeTitleEquals.mustache +0 -8
- package/docs/webapi/selectOption.mustache +0 -21
- package/docs/webapi/setCookie.mustache +0 -16
- package/docs/webapi/setGeoLocation.mustache +0 -12
- package/docs/webapi/switchTo.mustache +0 -9
- package/docs/webapi/switchToNextTab.mustache +0 -10
- package/docs/webapi/switchToPreviousTab.mustache +0 -10
- package/docs/webapi/type.mustache +0 -18
- package/docs/webapi/uncheckOption.mustache +0 -13
- package/docs/webapi/wait.mustache +0 -8
- package/docs/webapi/waitForClickable.mustache +0 -11
- package/docs/webapi/waitForDetached.mustache +0 -10
- package/docs/webapi/waitForElement.mustache +0 -11
- package/docs/webapi/waitForEnabled.mustache +0 -6
- package/docs/webapi/waitForFunction.mustache +0 -17
- package/docs/webapi/waitForInvisible.mustache +0 -10
- package/docs/webapi/waitForText.mustache +0 -13
- package/docs/webapi/waitForValue.mustache +0 -10
- package/docs/webapi/waitForVisible.mustache +0 -10
- package/docs/webapi/waitInUrl.mustache +0 -9
- package/docs/webapi/waitNumberOfVisibleElements.mustache +0 -10
- package/docs/webapi/waitToHide.mustache +0 -10
- package/docs/webapi/waitUrlEquals.mustache +0 -10
- package/docs/webdriver.md +0 -657
- package/docs/wiki/Books-&-Posts.md +0 -27
- package/docs/wiki/Community-Helpers-&-Plugins.md +0 -49
- package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +0 -29
- package/docs/wiki/Examples.md +0 -139
- package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -68
- package/docs/wiki/Home.md +0 -16
- package/docs/wiki/Release-Process.md +0 -24
- package/docs/wiki/Roadmap.md +0 -23
- package/docs/wiki/Tests.md +0 -1393
- package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -153
- package/docs/wiki/Videos.md +0 -19
package/docs/advanced.md
DELETED
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
permalink: /advanced
|
|
3
|
-
title: Advanced Usage
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Advanced Usage
|
|
7
|
-
|
|
8
|
-
## Data Driven Tests
|
|
9
|
-
|
|
10
|
-
Execute the same scenario on a different data set.
|
|
11
|
-
|
|
12
|
-
Let's say you want to test login for different user accounts.
|
|
13
|
-
In this case, you need to create a datatable and fill it in with credentials.
|
|
14
|
-
Then use `Data().Scenario` to include this data and generate multiple scenarios:
|
|
15
|
-
|
|
16
|
-
```js
|
|
17
|
-
// Define data table inside a test or load from another module
|
|
18
|
-
let accounts = new DataTable(['login', 'password']); //
|
|
19
|
-
accounts.add(['davert', '123456']); // adding records to a table
|
|
20
|
-
accounts.add(['admin', '123456']);
|
|
21
|
-
|
|
22
|
-
// You can skip some data. But add them to report as skipped (just like with usual scenarios):
|
|
23
|
-
accounts.xadd(['admin', '23456'])
|
|
24
|
-
|
|
25
|
-
// Pass dataTable to Data()
|
|
26
|
-
// Use special param `current` to get current data set
|
|
27
|
-
Data(accounts).Scenario('Test Login', ({ I, current }) => {
|
|
28
|
-
I.fillField('Username', current.login); // current is reserved!
|
|
29
|
-
I.fillField('Password', current.password);
|
|
30
|
-
I.click('Sign In');
|
|
31
|
-
I.see('Welcome '+ current.login);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// Also you can set only for Data tests. It will launch executes only the current test but with all data options
|
|
36
|
-
Data(accounts).only.Scenario('Test Login', ({ I, current }) => {
|
|
37
|
-
I.fillField('Username', current.login); // current is reserved!
|
|
38
|
-
I.fillField('Password', current.password);
|
|
39
|
-
I.click('Sign In');
|
|
40
|
-
I.see('Welcome '+ current.login);
|
|
41
|
-
});
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
*Important: you can't use name `current` for pageObjects or helpers in data scenarios*
|
|
45
|
-
|
|
46
|
-
This will produce 2 tests with different data sets.
|
|
47
|
-
Current data set is appended to a test name in output:
|
|
48
|
-
|
|
49
|
-
```sh
|
|
50
|
-
✓ Test Login | {"login":"davert","password":"123456"}
|
|
51
|
-
✓ Test Login | {"login":"admin","password":"123456"}
|
|
52
|
-
S Test Login | {"login":"admin","password":"23456"}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
```js
|
|
56
|
-
// You can filter your data table
|
|
57
|
-
Data(accounts.filter(account => account.login == 'admin')
|
|
58
|
-
.Scenario('Test Login', ({ I, current }) => {
|
|
59
|
-
I.fillField('Username', current.login);
|
|
60
|
-
I.fillField('Password', current.password);
|
|
61
|
-
I.click('Sign In');
|
|
62
|
-
I.see('Welcome '+ current.login);
|
|
63
|
-
});
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
This will limit data sets accoring passed function:
|
|
67
|
-
|
|
68
|
-
```sh
|
|
69
|
-
✓ Test Login | {"login":"admin","password":"123456"}
|
|
70
|
-
S Test Login | {"login":"admin","password":"23456"}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Data sets can also be defined with array, generator, or a function.
|
|
74
|
-
|
|
75
|
-
```js
|
|
76
|
-
Data(function*() {
|
|
77
|
-
yield { user: 'davert'};
|
|
78
|
-
yield { user: 'andrey'};
|
|
79
|
-
}).Scenario() // ...
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
*HINT: If you don't use DataTable. add `toString()` method to each object added to data set, so the data could be pretty printed in a test name*
|
|
83
|
-
|
|
84
|
-
## Tags
|
|
85
|
-
|
|
86
|
-
Append `@tag` to your test name, so
|
|
87
|
-
|
|
88
|
-
```js
|
|
89
|
-
Scenario('update user profile @slow')
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Alternativly, use `tag` method of Scenario to set additional tags:
|
|
93
|
-
|
|
94
|
-
```js
|
|
95
|
-
Scenario('update user profile', ({ }) => {
|
|
96
|
-
// test goes here
|
|
97
|
-
}).tag('@slow').tag('important');
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
All tests with `@tag` could be executed with `--grep '@tag'` option.
|
|
101
|
-
|
|
102
|
-
```sh
|
|
103
|
-
codeceptjs run --grep '@slow'
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Use regex for more flexible filtering:
|
|
107
|
-
|
|
108
|
-
* `--grep '(?=.*@smoke2)(?=.*@smoke3)'` - run tests with @smoke2 and @smoke3 in name
|
|
109
|
-
* `--grep "\@smoke2|\@smoke3"` - run tests with @smoke2 or @smoke3 in name
|
|
110
|
-
* `--grep '((?=.*@smoke2)(?=.*@smoke3))|@smoke4'` - run tests with (@smoke2 and @smoke3) or @smoke4 in name
|
|
111
|
-
* `--grep '(?=.*@smoke2)^(?!.*@smoke3)'` - run tests with @smoke2 but without @smoke3 in name
|
|
112
|
-
* `--grep '(?=.*)^(?!.*@smoke4)'` - run all tests except @smoke4
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
## Debug
|
|
117
|
-
|
|
118
|
-
CodeceptJS provides a debug mode in which additional information is printed.
|
|
119
|
-
It can be turned on with `--debug` flag.
|
|
120
|
-
|
|
121
|
-
```sh
|
|
122
|
-
npx codeceptjs run --debug
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
to receive even more information turn on `--verbose` flag:
|
|
126
|
-
|
|
127
|
-
```sh
|
|
128
|
-
npx codeceptjs run --verbose
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
> You can pause execution and enter **interactive console** mode by calling `pause()` inside your test.
|
|
132
|
-
|
|
133
|
-
To see a complete internal debug of CodeceptJS use `DEBUG` env variable:
|
|
134
|
-
|
|
135
|
-
```sh
|
|
136
|
-
DEBUG=codeceptjs:* npx codeceptjs run
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
For an interactive debugging use NodeJS debugger. In **WebStorm**:
|
|
140
|
-
|
|
141
|
-
```sh
|
|
142
|
-
node $NODE_DEBUG_OPTION ./node_modules/.bin/codeceptjs run
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
For **Visual Studio Code**, add the following configuration in launch.json:
|
|
146
|
-
|
|
147
|
-
```json
|
|
148
|
-
{
|
|
149
|
-
"type": "node",
|
|
150
|
-
"request": "launch",
|
|
151
|
-
"name": "codeceptjs",
|
|
152
|
-
"args": ["run", "--grep", "@your_test_tag"],
|
|
153
|
-
"program": "${workspaceFolder}/node_modules/.bin/codeceptjs"
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
## Test Options
|
|
159
|
-
|
|
160
|
-
Features and Scenarios have their options that can be set by passing a hash after their names:
|
|
161
|
-
|
|
162
|
-
```js
|
|
163
|
-
Feature('My feature', {key: val});
|
|
164
|
-
|
|
165
|
-
Scenario('My scenario', {key: val},({ I }) => {});
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
You can use this options for build your own [plugins](https://codecept.io/hooks/#plugins) with [event listners](https://codecept.io/hooks/#api). Example:
|
|
169
|
-
|
|
170
|
-
```js
|
|
171
|
-
// for test
|
|
172
|
-
event.dispatcher.on(event.test.before, (test) => {
|
|
173
|
-
...
|
|
174
|
-
if (test.opts.key) {
|
|
175
|
-
...
|
|
176
|
-
}
|
|
177
|
-
...
|
|
178
|
-
});
|
|
179
|
-
// or for suite
|
|
180
|
-
event.dispatcher.on(event.suite.before, (suite) => {
|
|
181
|
-
...
|
|
182
|
-
if (suite.opts.key) {
|
|
183
|
-
...
|
|
184
|
-
}
|
|
185
|
-
...
|
|
186
|
-
});
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
## Timeout
|
|
190
|
-
|
|
191
|
-
Tests can get stuck due to various reasons such as network connection issues, crashed browser, etc.
|
|
192
|
-
This can make tests process hang. To prevent these situations timeouts can be used. Timeouts can be set explicitly for flaky parts of code, or implicitly in a config.
|
|
193
|
-
|
|
194
|
-
> Previous timeout implementation was disabled as it had no effect when dealing with steps and promises.
|
|
195
|
-
|
|
196
|
-
### Steps Timeout
|
|
197
|
-
|
|
198
|
-
It is possible to limit a step execution to specified time with `I.limitTime` command.
|
|
199
|
-
It will set timeout in seconds for the next executed step:
|
|
200
|
-
|
|
201
|
-
```js
|
|
202
|
-
// limit clicking to 5 seconds
|
|
203
|
-
I.limitTime(5).click('Link')
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
It is possible to set a timeout for all steps implicitly (except waiters) using [stepTimeout plugin](/plugins/#steptimeout).
|
|
207
|
-
|
|
208
|
-
### Tests Timeout
|
|
209
|
-
|
|
210
|
-
Test timeout can be set in seconds via Scenario options:
|
|
211
|
-
|
|
212
|
-
```js
|
|
213
|
-
// limit test to 20 seconds
|
|
214
|
-
Scenario('slow test that should be stopped', { timeout: 20 }, ({ I }) => {
|
|
215
|
-
// ...
|
|
216
|
-
})
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
This timeout can be set globally in `codecept.conf.js` in seconds:
|
|
220
|
-
|
|
221
|
-
```js
|
|
222
|
-
exports.config = {
|
|
223
|
-
|
|
224
|
-
// each test must not run longer than 5 mins
|
|
225
|
-
timeout: 300,
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
### Suites Timeout
|
|
231
|
-
|
|
232
|
-
A timeout for a group of tests can be set on Feature level via options.
|
|
233
|
-
|
|
234
|
-
```js
|
|
235
|
-
// limit all tests in this suite to 30 seconds
|
|
236
|
-
Feature('flaky tests', { timeout: 30 })
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Timeout Confguration
|
|
240
|
-
|
|
241
|
-
<Badge text="Updated in 3.4" type="warning"/>
|
|
242
|
-
|
|
243
|
-
Timeout rules can be set globally via config.
|
|
244
|
-
|
|
245
|
-
To set a timeout for all running tests provide a **number of seconds** to `timeout` config option:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
```js
|
|
249
|
-
// inside codecept.conf.js or codecept.conf.ts
|
|
250
|
-
timeout: 30, // limit all tests in all suites to 30 secs
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
It is possible to tune this configuration for a different groups of tests passing options as array and using `grep` option to filter tests:
|
|
254
|
-
|
|
255
|
-
```js
|
|
256
|
-
// inside codecept.conf.js or codecept.conf.ts
|
|
257
|
-
|
|
258
|
-
timeout: [
|
|
259
|
-
10, // default timeout is 10secs
|
|
260
|
-
|
|
261
|
-
// but increase timeout for slow tests
|
|
262
|
-
{
|
|
263
|
-
grep: '@slow',
|
|
264
|
-
Feature: 50
|
|
265
|
-
},
|
|
266
|
-
]
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
> ℹ️ `grep` value can be string or regexp
|
|
270
|
-
|
|
271
|
-
It is possible to set a timeout for Scenario or Feature:
|
|
272
|
-
|
|
273
|
-
```js
|
|
274
|
-
// inside codecept.conf.js or codecept.conf.ts
|
|
275
|
-
timeout: [
|
|
276
|
-
|
|
277
|
-
// timeout for Feature with @slow in title
|
|
278
|
-
{
|
|
279
|
-
grep: '@slow',
|
|
280
|
-
Feature: 50
|
|
281
|
-
},
|
|
282
|
-
|
|
283
|
-
// timeout for Scenario with 'flaky0' .. `flaky1` in title
|
|
284
|
-
{
|
|
285
|
-
// regexp can be passed to grep
|
|
286
|
-
grep: /flaky[0-9]/,
|
|
287
|
-
Scenario: 10
|
|
288
|
-
},
|
|
289
|
-
|
|
290
|
-
// timeout for all suites
|
|
291
|
-
{
|
|
292
|
-
Feature: 20
|
|
293
|
-
}
|
|
294
|
-
]
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
Global timeouts will be overridden by explicit timeouts of a test or steps.
|
|
298
|
-
|
|
299
|
-
### Disable Timeouts
|
|
300
|
-
|
|
301
|
-
To execute tests ignoring all timeout settings use `--no-timeouts` option:
|
|
302
|
-
|
|
303
|
-
```
|
|
304
|
-
npx codeceptjs run --no-timeouts
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
## Dynamic Configuration
|
|
308
|
-
|
|
309
|
-
Helpers can be reconfigured per scenario or per feature.
|
|
310
|
-
This might be useful when some tests should be executed with different settings than others.
|
|
311
|
-
In order to reconfigure tests use `.config()` method of `Scenario` or `Feature`.
|
|
312
|
-
|
|
313
|
-
```js
|
|
314
|
-
Scenario('should be executed in firefox', ({ I }) => {
|
|
315
|
-
// I.amOnPage(..)
|
|
316
|
-
}).config({ browser: 'firefox' })
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
In this case `config` overrides current config of the first helper.
|
|
320
|
-
To change config of specific helper pass two arguments: helper name and config values:
|
|
321
|
-
|
|
322
|
-
```js
|
|
323
|
-
Scenario('should create data via v2 version of API', ({ I }) => {
|
|
324
|
-
// I.amOnPage(..)
|
|
325
|
-
}).config('REST', { endpoint: 'https://api.mysite.com/v2' })
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
Config can also be set by a function, in this case you can get a test object and specify config values based on it.
|
|
329
|
-
This is very useful when running tests against cloud providers, like BrowserStack. This function can also be asynchronous.
|
|
330
|
-
|
|
331
|
-
```js
|
|
332
|
-
Scenario('should report to BrowserStack', ({ I }) => {
|
|
333
|
-
// I.amOnPage(..)
|
|
334
|
-
}).config((test) => {
|
|
335
|
-
return { desiredCapabilities: {
|
|
336
|
-
project: test.suite.title,
|
|
337
|
-
name: test.title,
|
|
338
|
-
}}
|
|
339
|
-
});
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
Config changes can be applied to all tests in suite:
|
|
343
|
-
|
|
344
|
-
```js
|
|
345
|
-
Feature('Admin Panel').config({ url: 'https://mysite.com/admin' });
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
Please note that some config changes can't be applied on the fly. For instance, if you set `restart: false` in your config and then changing value `browser` won't take an effect as browser is already started and won't be closed untill all tests finish.
|
|
349
|
-
|
|
350
|
-
Configuration changes will be reverted after a test or a suite.
|
|
351
|
-
|
package/docs/api.md
DELETED
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
permalink: /api
|
|
3
|
-
title: API Testing
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## API Testing
|
|
7
|
-
|
|
8
|
-
CodeceptJS provides a way to write tests in declarative manner for REST and GraphQL APIs.
|
|
9
|
-
|
|
10
|
-
Take a look:
|
|
11
|
-
|
|
12
|
-
```js
|
|
13
|
-
I.sendGetRequest('/users/1');
|
|
14
|
-
// returns { "user": { "name": "jon" }, "projects": [] }
|
|
15
|
-
I.seeResponseCodeIsSuccessful();
|
|
16
|
-
I.seeResponseContainsKeys(['user', 'projects']);
|
|
17
|
-
I.seeResponseContainsJson({ user: { name: 'jon' } });
|
|
18
|
-
I.seeResponseMatchesJsonSchema($ => {
|
|
19
|
-
return $.object(
|
|
20
|
-
user: $.object({
|
|
21
|
-
name: $.string(),
|
|
22
|
-
}),
|
|
23
|
-
projects: $.array()
|
|
24
|
-
)
|
|
25
|
-
});
|
|
26
|
-
```
|
|
27
|
-
In this code we checked API request for:
|
|
28
|
-
|
|
29
|
-
* status code
|
|
30
|
-
* data inclusion
|
|
31
|
-
* data structure
|
|
32
|
-
|
|
33
|
-
These are the things you should generally test your APIs for.
|
|
34
|
-
|
|
35
|
-
> 🤓 It is recommended to check only invariable parts of responses. Check for required fields and only values you control. For instance, it is not recommended to check id fields, date fields, as they can be frequently changed.
|
|
36
|
-
|
|
37
|
-
## Installation
|
|
38
|
-
|
|
39
|
-
Install CodeceptJS if it is not installed yet.
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
npm i codeceptjs --save-dev
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Initialize CodeceptJS and select REST or GraphQL helper when asked for a helper:
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
npx codeceptjs init
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Configuration
|
|
52
|
-
|
|
53
|
-
Ensure that inside `codecept.conf.js` in helpers section `REST` or `GraphQL` helpers are enabled.
|
|
54
|
-
|
|
55
|
-
* If you use `REST` helper add `JSONResponse` helper below with no extra config:
|
|
56
|
-
|
|
57
|
-
```js
|
|
58
|
-
// inside codecept.conf.js
|
|
59
|
-
// ...
|
|
60
|
-
helpers: {
|
|
61
|
-
REST: {
|
|
62
|
-
endpoint: 'http://localhost:3000/api'
|
|
63
|
-
},
|
|
64
|
-
// .. add JSONResponse helper here
|
|
65
|
-
JSONResponse: {}
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
* If you use `GraphQL` helper add `JSONResponse` helper, configuring it to use GraphQL for requests:
|
|
69
|
-
|
|
70
|
-
```js
|
|
71
|
-
helpers: {
|
|
72
|
-
GraphQL: {
|
|
73
|
-
endpoint: 'http://localhost:3000/graphql'
|
|
74
|
-
},
|
|
75
|
-
// .. add JSONResponse helper here
|
|
76
|
-
JSONResponse: {
|
|
77
|
-
requestHelper: 'GraphQL',
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Originally, REST and GraphQL helpers were not designed for API testing.
|
|
83
|
-
They were used to perform API requests for browser tests. As so, they lack assertion methods to API responses.
|
|
84
|
-
|
|
85
|
-
[`JSONResponse`](/helpers/JSONResponse/) helper adds response assertions.
|
|
86
|
-
|
|
87
|
-
> 💡 In CodeceptJS assertions start with `see` prefix. Learn more about assertions by [opening reference for JSONResponse](/helpers/JSONResponse/) helper.
|
|
88
|
-
|
|
89
|
-
Generate TypeScript definitions to get auto-completions for JSONResponse:
|
|
90
|
-
|
|
91
|
-
```
|
|
92
|
-
npx codeceptjs def
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
After helpers were configured and typings were generated, you can start writing first API test. By default, CodeceptJS saves tests in `tests` directory and uses `*_test.js` suffix. The `init` command created the first test for you to start.
|
|
96
|
-
|
|
97
|
-
> Check [API Examples](https://github.com/codeceptjs/api-examples) to see tests implementations.
|
|
98
|
-
|
|
99
|
-
## Requests
|
|
100
|
-
|
|
101
|
-
[REST](/helpers/REST/) or [GraphQL](/helpers/GraphQL/) helpers implement methods for making API requests.
|
|
102
|
-
Both helpers send requests via HTTP protocol from CodeceptJS process.
|
|
103
|
-
For most cases, you will need to have authentication. It can be passed via headers, which can be added to helper's configuration in `codecept.conf.js`.
|
|
104
|
-
|
|
105
|
-
```js
|
|
106
|
-
helpers: {
|
|
107
|
-
REST: {
|
|
108
|
-
defaultHeaders: {
|
|
109
|
-
// use Bearer Authorization
|
|
110
|
-
'Authorization': 'Bearer 11111',
|
|
111
|
-
'Content-Type': 'application/json',
|
|
112
|
-
'Accept': 'application/json',
|
|
113
|
-
},
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Or you can use the browser cookies if you are running browser session.
|
|
119
|
-
In this case use `setSharedCookies()` from `@codeceptjs/configure` package:
|
|
120
|
-
|
|
121
|
-
```js
|
|
122
|
-
const { setSharedCookies } = require('@codeceptjs/configure');
|
|
123
|
-
|
|
124
|
-
// add this before exports.config
|
|
125
|
-
setSharedCookies();
|
|
126
|
-
|
|
127
|
-
exports.config = {
|
|
128
|
-
// ...
|
|
129
|
-
helpers: {
|
|
130
|
-
// also works with Playwright or Puppeteer
|
|
131
|
-
WebDriver: {
|
|
132
|
-
//...
|
|
133
|
-
},
|
|
134
|
-
|
|
135
|
-
REST: {
|
|
136
|
-
// ...
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### REST
|
|
143
|
-
|
|
144
|
-
REST helper can send GET/POST/PATCH/etc requests to REST API endpoint:
|
|
145
|
-
|
|
146
|
-
* [`I.sendGetRequest()`](/helpers/REST#sendGetRequest)
|
|
147
|
-
* [`I.sendPostRequest()`](/helpers/REST#sendPostRequest)
|
|
148
|
-
* [`I.sendPutRequest()`](/helpers/REST#sendPutRequest)
|
|
149
|
-
* [`I.sendPatchRequest()`](/helpers/REST#sendPatchRequest)
|
|
150
|
-
* [`I.sendDeleteRequest()`](/helpers/REST#sendDeleteRequest)
|
|
151
|
-
* ...
|
|
152
|
-
|
|
153
|
-
Authentication headers can be set in [helper's config](https://codecept.io/helpers/REST/#configuration) or per test with headers or special methods like `I.amBearerAuthenticated`.
|
|
154
|
-
|
|
155
|
-
Example:
|
|
156
|
-
|
|
157
|
-
```js
|
|
158
|
-
Feature('Users endpoint')
|
|
159
|
-
|
|
160
|
-
Scenario('create user', ({ I }) => {
|
|
161
|
-
// this way we pass Bearer token
|
|
162
|
-
I.amBearerAuthenticated(secret('token-is-here'));
|
|
163
|
-
// for custom authorization with headers use
|
|
164
|
-
// I.haveRequestHeaders method
|
|
165
|
-
|
|
166
|
-
// here we send a POST request
|
|
167
|
-
const response = await I.sendPostRequest('/users', {
|
|
168
|
-
name: 'joe',
|
|
169
|
-
email: 'joe@mail.com'
|
|
170
|
-
});
|
|
171
|
-
// usually we won't need direct access to response object for API testing
|
|
172
|
-
// but you can obtain it from request
|
|
173
|
-
|
|
174
|
-
// check the last request was successful
|
|
175
|
-
// this method introduced by JSONResponse helper
|
|
176
|
-
I.seeResponseCodeIsSuccessful();
|
|
177
|
-
})
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
### GraphQL
|
|
181
|
-
|
|
182
|
-
GraphQL have request format different then in REST API, but the response format is the same.
|
|
183
|
-
It's plain old JSON. This why `JSONResponse` helper works for both API types.
|
|
184
|
-
Configure authorization headers in `codecept.conf.js` and make your first query:
|
|
185
|
-
|
|
186
|
-
```js
|
|
187
|
-
Feature('Users endpoint')
|
|
188
|
-
|
|
189
|
-
Scenario('get user by query', ({ I }) => {
|
|
190
|
-
// make GraphQL query or mutation
|
|
191
|
-
const resp = await I.sendQuery('{ user(id: 0) { id name email }}');
|
|
192
|
-
I.seeResponseCodeIsSuccessful();
|
|
193
|
-
|
|
194
|
-
// GraphQL always returns key data as part of response
|
|
195
|
-
I.seeResponseContainsKeys(['data']);
|
|
196
|
-
|
|
197
|
-
// check data for partial inclusion
|
|
198
|
-
I.seeResponseContainsJson({
|
|
199
|
-
data: {
|
|
200
|
-
user: {
|
|
201
|
-
name: 'john doe',
|
|
202
|
-
email: 'johnd@mutex.com',
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
GraphQL helper has two methods available:
|
|
210
|
-
|
|
211
|
-
* [`I.sendQuery()`](/helpers/GraphQL#sendQuery)
|
|
212
|
-
* [`I.sendMutation()`](/helpers/GraphQL#sendMutation)
|
|
213
|
-
|
|
214
|
-
## Assertions
|
|
215
|
-
|
|
216
|
-
`JSONResponse` provides set of assertions for responses in JSON format. These assertions were designed to check only invariable parts of responses. So instead of checking that response equals to the one provided, we will check for data inclusion and structure matching.
|
|
217
|
-
|
|
218
|
-
For most of cases, you won't need to perform assertions by accessing `response` object directly. All assretions are performed under hood inside `JSONResponse` module. It is recommended to keep it that way, to keep tests readable and make test log to contain all assertions.
|
|
219
|
-
|
|
220
|
-
```js
|
|
221
|
-
Scenario('I make API call', ({ I }) => {
|
|
222
|
-
// request was made by REST
|
|
223
|
-
// or by GraphQL helper
|
|
224
|
-
|
|
225
|
-
// check that response code is 2xx
|
|
226
|
-
I.seeResponseCodeIsSuccessful();
|
|
227
|
-
|
|
228
|
-
// check that response contains keys
|
|
229
|
-
I.seeResponseContainsKeys(['data', 'pages', 'meta']);
|
|
230
|
-
});
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### Response Status Codes
|
|
234
|
-
|
|
235
|
-
[Response status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) can be checked to be equal to some value or to be in a specific range.
|
|
236
|
-
To check that response code is `200` call `I.seeResponseCodeIs`:
|
|
237
|
-
|
|
238
|
-
```js
|
|
239
|
-
I.seeResponseCodeIs(200);
|
|
240
|
-
```
|
|
241
|
-
But because other response codes in 2xx range are also valid responses, you can use `seeResponseCodeIsSuccessful()` which will match 200 (OK), 201 (Created), 206 (Partial Content) and others. Methods to check 3xx, 4xx, 5xx response statuses also available.
|
|
242
|
-
|
|
243
|
-
```js
|
|
244
|
-
// matches 200, 201, 202, ... 206
|
|
245
|
-
I.seeResponseCodeIsSuccessful();
|
|
246
|
-
|
|
247
|
-
// matches 300...308
|
|
248
|
-
I.seeResponseCodeIsRedirection();
|
|
249
|
-
|
|
250
|
-
// matches 400..451
|
|
251
|
-
I.seeResponseCodeIsClientError();
|
|
252
|
-
|
|
253
|
-
// matches 500-511
|
|
254
|
-
I.seeResponseCodeIsServerError();
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
### Structure
|
|
258
|
-
|
|
259
|
-
The most basic thing to check in response is existence of keys in JSON object. Use [`I.seeResponseContainsKeys()`](/helpers/JSONResponse#seeResponseContainsKeys) method for it:
|
|
260
|
-
|
|
261
|
-
```js
|
|
262
|
-
// response is { "name": "joe", "email": "joe@joe.com" }
|
|
263
|
-
I.seeResponseContainsKeys(['name', 'email']);
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
> ℹ️ If response is an array, it will check that every element in array have provided keys
|
|
267
|
-
|
|
268
|
-
However, this is a very naive approach. It won't work for arrays or nested objects.
|
|
269
|
-
To check complex JSON structures `JSONResponse` helper uses [`joi`](https://joi.dev) library.
|
|
270
|
-
It has rich API to validate JSON by the schema defined using JavaScript.
|
|
271
|
-
|
|
272
|
-
```js
|
|
273
|
-
// require joi library,
|
|
274
|
-
// it is installed with CodeceptJS
|
|
275
|
-
const Joi = require('joi');
|
|
276
|
-
|
|
277
|
-
// create schema definition using Joi API
|
|
278
|
-
const schema = Joi.object().keys({
|
|
279
|
-
email: Joi.string().email().required(),
|
|
280
|
-
phone: Joi.string().regex(/^\d{3}-\d{3}-\d{4}$/).required(),
|
|
281
|
-
birthday: Joi.date().max('1-1-2004').iso()
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
// check that response matches that schema
|
|
285
|
-
I.seeResponseMatchesJsonSchema(schema);
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
### Data Inclusion
|
|
289
|
-
|
|
290
|
-
To check that response contains expected data use `I.seeResponseContainsJson` method.
|
|
291
|
-
It will check the response data for partial match.
|
|
292
|
-
|
|
293
|
-
```js
|
|
294
|
-
I.seeResponseContainsJson({
|
|
295
|
-
user: {
|
|
296
|
-
email: 'user@user.com'
|
|
297
|
-
}
|
|
298
|
-
})
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
> ℹ️ If response is an array, it will check that at least one element in array matches JSON
|
|
302
|
-
|
|
303
|
-
To perform arbitrary assertions on a response object use `seeResponseValidByCallback`.
|
|
304
|
-
It allows you to do any kind of assertions by using `expect` from [`chai`](https://www.chaijs.com) library.
|
|
305
|
-
|
|
306
|
-
```js
|
|
307
|
-
I.seeResponseValidByCallback(({ data, status, expect }) => {
|
|
308
|
-
// we receive data and expect to combine them for good assertion
|
|
309
|
-
expect(data.users.length).to.be.gte(10);
|
|
310
|
-
})
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
## Extending JSONResponse
|
|
314
|
-
|
|
315
|
-
To add more assertions it is recommended to create a custom helper.
|
|
316
|
-
Inside it you can get access to latest JSON response:
|
|
317
|
-
|
|
318
|
-
```js
|
|
319
|
-
// inside a custom helper
|
|
320
|
-
makeSomeCustomAssertion() {
|
|
321
|
-
const response = this.helpers.JSONResponse.response;
|
|
322
|
-
}
|
|
323
|
-
```
|