codeceptjs 2.3.4 → 2.4.1
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/CHANGELOG.md +52 -0
- package/README.md +28 -6
- package/bin/codecept.js +42 -0
- package/docs/advanced.md +45 -1
- package/docs/angular.md +3 -3
- package/docs/basics.md +162 -118
- package/docs/bdd.md +30 -5
- package/docs/best.md +8 -6
- package/docs/books.md +6 -1
- package/docs/build/Appium.js +95 -85
- package/docs/build/FileSystem.js +48 -3
- package/docs/build/GraphQL.js +3 -2
- package/docs/build/GraphQLDataFactory.js +1 -0
- package/docs/build/Mochawesome.js +3 -2
- package/docs/build/MockRequest.js +23 -5
- package/docs/build/Nightmare.js +87 -128
- package/docs/build/Protractor.js +107 -155
- package/docs/build/Puppeteer.js +190 -174
- package/docs/build/REST.js +13 -9
- package/docs/build/SeleniumWebdriver.js +0 -17
- package/docs/build/TestCafe.js +164 -158
- package/docs/build/WebDriver.js +236 -211
- package/docs/build/WebDriverIO.js +218 -187
- package/docs/changelog.md +57 -1
- package/docs/commands.md +41 -2
- package/docs/community-helpers.md +12 -1
- package/docs/configuration.md +5 -2
- package/docs/continuous-integration.md +22 -0
- package/docs/{helpers.md → custom-helpers.md} +16 -10
- package/docs/data.md +7 -6
- package/docs/detox.md +6 -6
- package/docs/email.md +4 -2
- package/docs/examples.md +22 -3
- package/docs/helpers/ApiDataFactory.md +15 -13
- package/docs/helpers/Appium.md +1011 -468
- package/docs/helpers/Detox.md +33 -26
- package/docs/helpers/FileSystem.md +43 -13
- package/docs/helpers/GraphQL.md +17 -15
- package/docs/helpers/GraphQLDataFactory.md +15 -13
- package/docs/helpers/Mochawesome.md +3 -1
- package/docs/helpers/MockRequest.md +37 -19
- package/docs/helpers/Nightmare.md +129 -240
- package/docs/helpers/Polly.md +1 -1
- package/docs/helpers/Protractor.md +157 -298
- package/docs/helpers/Puppeteer.md +216 -335
- package/docs/helpers/REST.md +29 -24
- package/docs/helpers/TestCafe.md +137 -235
- package/docs/helpers/WebDriver.md +250 -347
- package/docs/hooks.md +14 -10
- package/docs/index.md +112 -0
- package/docs/installation.md +3 -1
- package/docs/locators.md +19 -8
- package/docs/mobile-react-native-locators.md +2 -2
- package/docs/mobile.md +5 -3
- package/docs/nightmare.md +2 -1
- package/docs/pageobjects.md +4 -2
- package/docs/parallel.md +4 -2
- package/docs/plugins.md +41 -15
- package/docs/puppeteer.md +8 -6
- package/docs/quickstart.md +130 -0
- package/docs/react.md +4 -2
- package/docs/reports.md +6 -4
- package/docs/testcafe.md +10 -8
- package/docs/translation.md +4 -2
- package/docs/ui.md +56 -0
- package/docs/videos.md +11 -2
- package/docs/visual.md +7 -5
- package/docs/vue.md +121 -0
- package/docs/webapi/appendField.mustache +1 -1
- package/docs/webapi/attachFile.mustache +1 -1
- package/docs/webapi/checkOption.mustache +2 -2
- package/docs/webapi/clearCookie.mustache +1 -1
- package/docs/webapi/click.mustache +2 -2
- package/docs/webapi/clickLink.mustache +2 -2
- package/docs/webapi/dontSee.mustache +1 -2
- package/docs/webapi/dontSeeCheckboxIsChecked.mustache +1 -1
- package/docs/webapi/dontSeeElement.mustache +1 -1
- package/docs/webapi/dontSeeElementInDOM.mustache +1 -1
- package/docs/webapi/dontSeeInField.mustache +1 -1
- package/docs/webapi/doubleClick.mustache +2 -2
- package/docs/webapi/downloadFile.mustache +1 -1
- package/docs/webapi/dragSlider.mustache +1 -1
- package/docs/webapi/executeAsyncScript.mustache +2 -1
- package/docs/webapi/executeScript.mustache +2 -1
- package/docs/webapi/fillField.mustache +1 -1
- package/docs/webapi/grabAttributeFrom.mustache +1 -1
- package/docs/webapi/grabBrowserLogs.mustache +1 -1
- package/docs/webapi/grabCookie.mustache +2 -2
- package/docs/webapi/grabCssPropertyFrom.mustache +1 -1
- package/docs/webapi/grabHTMLFrom.mustache +1 -1
- package/docs/webapi/grabNumberOfVisibleElements.mustache +1 -1
- package/docs/webapi/grabPageScrollPosition.mustache +1 -1
- package/docs/webapi/grabTextFrom.mustache +2 -2
- package/docs/webapi/grabValueFrom.mustache +1 -1
- package/docs/webapi/moveCursorTo.mustache +3 -3
- package/docs/webapi/pressKey.mustache +1 -1
- package/docs/webapi/pressKeyWithKeyNormalization.mustache +1 -1
- package/docs/webapi/rightClick.mustache +2 -2
- package/docs/webapi/saveScreenshot.mustache +1 -1
- package/docs/webapi/scrollIntoView.mustache +10 -0
- package/docs/webapi/scrollTo.mustache +3 -3
- package/docs/webapi/see.mustache +1 -1
- package/docs/webapi/seeAttributesOnElements.mustache +1 -1
- package/docs/webapi/seeCheckboxIsChecked.mustache +1 -1
- package/docs/webapi/seeCssPropertiesOnElements.mustache +1 -1
- package/docs/webapi/seeElement.mustache +1 -1
- package/docs/webapi/seeElementInDOM.mustache +1 -1
- package/docs/webapi/seeInField.mustache +1 -1
- package/docs/webapi/seeNumberOfElements.mustache +1 -1
- package/docs/webapi/seeNumberOfVisibleElements.mustache +1 -1
- package/docs/webapi/seeTextEquals.mustache +8 -0
- package/docs/webapi/selectOption.mustache +2 -2
- package/docs/webapi/switchTo.mustache +1 -1
- package/docs/webapi/uncheckOption.mustache +2 -2
- package/docs/webapi/waitForClickable.mustache +10 -0
- package/docs/webapi/waitForDetached.mustache +2 -2
- package/docs/webapi/waitForElement.mustache +2 -2
- package/docs/webapi/waitForEnabled.mustache +2 -2
- package/docs/webapi/waitForFunction.mustache +2 -2
- package/docs/webapi/waitForInvisible.mustache +2 -2
- package/docs/webapi/waitForText.mustache +2 -2
- package/docs/webapi/waitForValue.mustache +1 -1
- package/docs/webapi/waitForVisible.mustache +2 -2
- package/docs/webapi/waitInUrl.mustache +1 -1
- package/docs/webapi/waitNumberOfVisibleElements.mustache +2 -2
- package/docs/webapi/waitToHide.mustache +2 -2
- package/docs/webapi/waitUntil.mustache +3 -2
- package/docs/webapi/waitUrlEquals.mustache +1 -1
- package/docs/webdriver.md +20 -18
- package/docs/wiki/.git/FETCH_HEAD +1 -0
- package/docs/wiki/.git/HEAD +1 -0
- package/docs/wiki/.git/ORIG_HEAD +1 -0
- package/docs/wiki/.git/config +11 -0
- package/docs/wiki/.git/description +1 -0
- package/docs/wiki/.git/hooks/applypatch-msg.sample +15 -0
- package/docs/wiki/.git/hooks/commit-msg.sample +24 -0
- package/docs/wiki/.git/hooks/fsmonitor-watchman.sample +114 -0
- package/docs/wiki/.git/hooks/post-update.sample +8 -0
- package/docs/wiki/.git/hooks/pre-applypatch.sample +14 -0
- package/docs/wiki/.git/hooks/pre-commit.sample +49 -0
- package/docs/wiki/.git/hooks/pre-push.sample +53 -0
- package/docs/wiki/.git/hooks/pre-rebase.sample +169 -0
- package/docs/wiki/.git/hooks/pre-receive.sample +24 -0
- package/docs/wiki/.git/hooks/prepare-commit-msg.sample +42 -0
- package/docs/wiki/.git/hooks/update.sample +128 -0
- package/docs/wiki/.git/index +0 -0
- package/docs/wiki/.git/info/exclude +6 -0
- package/docs/wiki/.git/logs/HEAD +4 -0
- package/docs/wiki/.git/logs/refs/heads/master +4 -0
- package/docs/wiki/.git/logs/refs/remotes/origin/HEAD +1 -0
- package/docs/wiki/.git/logs/refs/remotes/origin/master +3 -0
- package/docs/wiki/.git/objects/00/d216b0774d15db2d0a2a0d4ce249b5251acc55 +3 -0
- package/docs/wiki/.git/objects/09/01d87c5241905fdfe3493cfe8f04df4a2685ea +0 -0
- package/docs/wiki/.git/objects/0d/bdd0c20c4deb6a8cc81dbbf32ecf8c09238983 +2 -0
- package/docs/wiki/.git/objects/1a/c29e4fa82422c52392f22f0f2b8d1a759535bf +0 -0
- package/docs/wiki/.git/objects/27/12f92898d3e8f68e229b6cda76570d6c66d781 +0 -0
- package/docs/wiki/.git/objects/2d/dbe22c257166b648928eeb9460ecfb71ba702d +0 -0
- package/docs/wiki/.git/objects/2f/c942ec3773efd2678d9ff98035c61fcded81a1 +0 -0
- package/docs/wiki/.git/objects/40/a2856342c67796b48911a256b764fb06888b94 +5 -0
- package/docs/wiki/.git/objects/47/53181844fc4dc563cf3aa5e80462243cb58d38 +0 -0
- package/docs/wiki/.git/objects/4e/24a95fb2e4f8ffef51f19b694451a205c06f10 +3 -0
- package/docs/wiki/.git/objects/73/31ebd96f3c7e08a9f63f05a25f939afa0d4de1 +0 -0
- package/docs/wiki/.git/objects/86/19cbb2289caa502e33fccf0ed14eecf6ba2ba0 +0 -0
- package/docs/wiki/.git/objects/a4/72f797d9d74b87c9f71a2b1539d75bb07d1e35 +0 -0
- package/docs/wiki/.git/objects/c9/9f3e4bd227d6b050b2e416f9876df49583dbf6 +0 -0
- package/docs/wiki/.git/objects/ca/e609b4ef3e0ef85fcbe0d68d1a58246584b915 +0 -0
- package/docs/wiki/.git/objects/d5/8386ca72f6d550548f3d71d74e3ac73d5ad488 +0 -0
- package/docs/wiki/.git/objects/d9/c6874a6de524bdafeb563a20d847f4fdd59a86 +0 -0
- package/docs/wiki/.git/objects/f1/c944675bb38b40ae553b0be36c14674c79af54 +0 -0
- package/docs/wiki/.git/objects/pack/pack-28da0fc7e6c08d4c5350717bfbb7b1c53e8198ad.idx +0 -0
- package/docs/wiki/.git/objects/pack/pack-28da0fc7e6c08d4c5350717bfbb7b1c53e8198ad.pack +0 -0
- package/docs/wiki/.git/packed-refs +2 -0
- package/docs/wiki/.git/refs/heads/master +1 -0
- package/docs/wiki/.git/refs/remotes/origin/HEAD +1 -0
- package/docs/wiki/.git/refs/remotes/origin/master +1 -0
- package/docs/wiki/Books-&-Posts.md +27 -0
- package/docs/wiki/Community-Helpers.md +41 -0
- package/docs/wiki/Examples.md +138 -0
- package/docs/wiki/Home.md +11 -0
- package/docs/wiki/Release-process.md +25 -0
- package/docs/wiki/Roadmap.md +23 -0
- package/docs/wiki/Videos.md +19 -0
- package/lib/actor.js +18 -1
- package/lib/assert/error.js +3 -3
- package/lib/codecept.js +9 -6
- package/lib/command/configMigrate.js +7 -6
- package/lib/command/definitions.js +98 -350
- package/lib/command/generate.js +22 -17
- package/lib/command/gherkin/init.js +2 -1
- package/lib/command/gherkin/snippets.js +6 -6
- package/lib/command/gherkin/steps.js +0 -1
- package/lib/command/info.js +40 -0
- package/lib/command/init.js +54 -41
- package/lib/command/run-multiple.js +5 -4
- package/lib/command/run-rerun.js +39 -0
- package/lib/command/run-workers.js +4 -6
- package/lib/command/run.js +8 -18
- package/lib/command/utils.js +23 -2
- package/lib/command/workers/runTests.js +1 -2
- package/lib/config.js +10 -4
- package/lib/container.js +31 -6
- package/lib/data/dataTableArgument.js +31 -0
- package/lib/data/table.js +4 -0
- package/lib/event.js +65 -1
- package/lib/helper/Appium.js +52 -38
- package/lib/helper/FileSystem.js +48 -3
- package/lib/helper/GraphQL.js +3 -2
- package/lib/helper/GraphQLDataFactory.js +1 -0
- package/lib/helper/Mochawesome.js +3 -2
- package/lib/helper/MockRequest.js +23 -5
- package/lib/helper/Nightmare.js +5 -6
- package/lib/helper/Protractor.js +7 -8
- package/lib/helper/Puppeteer.js +76 -20
- package/lib/helper/REST.js +13 -9
- package/lib/helper/SeleniumWebdriver.js +0 -17
- package/lib/helper/TestCafe.js +84 -36
- package/lib/helper/WebDriver.js +113 -59
- package/lib/helper/WebDriverIO.js +43 -59
- package/lib/helper/clientscripts/nightmare.js +66 -4
- package/lib/helper/scripts/isElementClickable.js +24 -0
- package/lib/helper.js +34 -10
- package/lib/history.js +1 -1
- package/lib/hooks.js +2 -1
- package/lib/index.js +19 -0
- package/lib/interfaces/bdd.js +4 -0
- package/lib/interfaces/featureConfig.js +10 -3
- package/lib/interfaces/gherkin.js +6 -2
- package/lib/interfaces/scenarioConfig.js +17 -6
- package/lib/listener/config.js +1 -1
- package/lib/listener/exit.js +6 -0
- package/lib/listener/steps.js +0 -1
- package/lib/listener/trace.js +0 -1
- package/lib/locator.js +67 -2
- package/lib/output.js +53 -0
- package/lib/parser.js +2 -71
- package/lib/pause.js +3 -2
- package/lib/plugin/allure.js +41 -22
- package/lib/plugin/autoLogin.js +4 -1
- package/lib/plugin/pauseOnFail.js +38 -0
- package/lib/plugin/puppeteerCoverage.js +8 -7
- package/lib/plugin/screenshotOnFail.js +13 -8
- package/lib/plugin/stepByStepReport.js +7 -6
- package/lib/plugin/wdio.js +2 -1
- package/lib/recorder.js +85 -7
- package/lib/rerun.js +81 -0
- package/lib/secret.js +6 -0
- package/lib/session.js +9 -2
- package/lib/step.js +37 -2
- package/lib/store.js +5 -1
- package/lib/ui.js +34 -8
- package/lib/utils.js +6 -13
- package/lib/within.js +5 -0
- package/package.json +49 -29
- package/typings/Mocha.d.ts +21 -0
- package/typings/Protractor.d.ts +16 -0
- package/typings/index.d.ts +169 -0
- package/typings/jsdoc.conf.js +34 -0
- package/typings/jsdoc.namespace.js +29 -0
- package/typings/types.d.ts +9827 -0
- package/typings/utils.d.ts +7 -0
- package/docs/acceptance.md +0 -409
- package/docs/api/codecept.md +0 -75
- package/docs/api/config.md +0 -49
- package/docs/api/container.md +0 -66
- package/docs/api/helper.md +0 -116
- package/docs/api/output.md +0 -67
- package/docs/api/recorder.md +0 -63
- package/docs/helpers/SeleniumWebdriver.md +0 -92
- package/docs/helpers/WebDriverIO.md +0 -1671
|
@@ -1,301 +1,29 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
1
4
|
const getConfig = require('./utils').getConfig;
|
|
2
5
|
const getTestRoot = require('./utils').getTestRoot;
|
|
3
6
|
const Codecept = require('../codecept');
|
|
4
7
|
const container = require('../container');
|
|
5
|
-
const methodsOfObject = require('../utils').methodsOfObject;
|
|
6
8
|
const output = require('../output');
|
|
7
|
-
const { toTypeDef } = require('../parser');
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
|
|
11
|
-
const template = `
|
|
12
|
-
type ICodeceptCallback = (i?: CodeceptJS.{{I}}, current?:any{{callbackParams}}) => void;
|
|
13
|
-
|
|
14
|
-
declare class FeatureConfig {
|
|
15
|
-
retry(times: number): FeatureConfig
|
|
16
|
-
timeout(seconds: number): FeatureConfig
|
|
17
|
-
config(config: object): FeatureConfig
|
|
18
|
-
config(helperName: string, config: object): FeatureConfig
|
|
19
|
-
tag(tagName: string): FeatureConfig
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
declare class ScenarioConfig {
|
|
23
|
-
throws(err: any): ScenarioConfig;
|
|
24
|
-
fails(): ScenarioConfig;
|
|
25
|
-
retry(times: number): ScenarioConfig
|
|
26
|
-
timeout(timeout: number): ScenarioConfig
|
|
27
|
-
inject(inject: object): ScenarioConfig
|
|
28
|
-
config(config: object): ScenarioConfig
|
|
29
|
-
config(helperName: string, config: object): ScenarioConfig
|
|
30
|
-
tag(tagName: string): ScenarioConfig
|
|
31
|
-
injectDependencies(dependencies: { [key: string]: any }): ScenarioConfig
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface ILocator {
|
|
35
|
-
id?: string;
|
|
36
|
-
xpath?: string;
|
|
37
|
-
css?: string;
|
|
38
|
-
name?: string;
|
|
39
|
-
frame?: string;
|
|
40
|
-
android?: string;
|
|
41
|
-
ios?: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
type LocatorOrString = string | ILocator | Locator;
|
|
45
|
-
|
|
46
|
-
declare class Container {
|
|
47
|
-
create(config: Object, opts: Object): void
|
|
48
|
-
plugins(name?: string): Object
|
|
49
|
-
support(name?: string): Object
|
|
50
|
-
helpers(name?: string): Object
|
|
51
|
-
translation(): Object
|
|
52
|
-
mocha(): Object
|
|
53
|
-
append(newContainer: Object): void
|
|
54
|
-
clear(newHelpers: Object, newSupport: Object, newPlugins: Object): void
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
declare class RecorderSession {
|
|
58
|
-
running: boolean
|
|
59
|
-
start(name: string): void
|
|
60
|
-
restore(name: string): void
|
|
61
|
-
catch(fn: CallableFunction): void
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
declare class Recorder {
|
|
65
|
-
retries: Object[]
|
|
66
|
-
start(): void
|
|
67
|
-
isRunning(): boolean
|
|
68
|
-
startUnlessRunning(): void
|
|
69
|
-
errHandler(fn: CallableFunction): void
|
|
70
|
-
reset(): void
|
|
71
|
-
session: RecorderSession
|
|
72
|
-
add(taskName: string, fn?: CallableFunction, force?: boolean, retry?: boolean): Promise<any>
|
|
73
|
-
retry(opts: Object): Promise<any>
|
|
74
|
-
catch(customErrFn: CallableFunction): Promise<any>
|
|
75
|
-
catchWithoutStop(customErrFn: CallableFunction ): Promise<any>
|
|
76
|
-
throw(err: Error): Promise<any>
|
|
77
|
-
saveFirstAsyncError(err: Error): void
|
|
78
|
-
getAsyncErr(): Promise<Error>
|
|
79
|
-
cleanAsyncErr(): void
|
|
80
|
-
stop():void
|
|
81
|
-
promise(): Promise<any>
|
|
82
|
-
scheduled(): string[]
|
|
83
|
-
toString(): string
|
|
84
|
-
add(hookName: string, fn: CallableFunction, force?: boolean): void
|
|
85
|
-
catch(customErrFn: CallableFunction): void
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
declare class CodeceptJSEvent {
|
|
89
|
-
dispatcher: NodeJS.EventEmitter
|
|
90
|
-
test: {
|
|
91
|
-
started: string
|
|
92
|
-
before: string
|
|
93
|
-
after: string
|
|
94
|
-
passed: string
|
|
95
|
-
failed: string
|
|
96
|
-
finished: string
|
|
97
|
-
}
|
|
98
|
-
suite: {
|
|
99
|
-
before: string,
|
|
100
|
-
after: string,
|
|
101
|
-
}
|
|
102
|
-
hook: {
|
|
103
|
-
started: string,
|
|
104
|
-
passed: string,
|
|
105
|
-
}
|
|
106
|
-
step: {
|
|
107
|
-
before: string,
|
|
108
|
-
after: string,
|
|
109
|
-
started: string,
|
|
110
|
-
passed: string,
|
|
111
|
-
failed: string,
|
|
112
|
-
finished: string,
|
|
113
|
-
}
|
|
114
|
-
all: {
|
|
115
|
-
before: string,
|
|
116
|
-
after: string,
|
|
117
|
-
result: string,
|
|
118
|
-
}
|
|
119
|
-
multiple: {
|
|
120
|
-
before: string,
|
|
121
|
-
after: string,
|
|
122
|
-
}
|
|
123
|
-
emit(event: string, param: string): void
|
|
124
|
-
cleanDispatcher(): void
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
declare class Output {
|
|
128
|
-
colors: any
|
|
129
|
-
styles: {
|
|
130
|
-
error: any,
|
|
131
|
-
success: any,
|
|
132
|
-
scenario: any,
|
|
133
|
-
basic: any,
|
|
134
|
-
debug: any,
|
|
135
|
-
log: any,
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
print(msg: string): void
|
|
139
|
-
stepShift: number
|
|
140
|
-
level(level: number): number
|
|
141
|
-
process(process: string): string
|
|
142
|
-
debug(msg: string): void
|
|
143
|
-
log(msg: string): void
|
|
144
|
-
error(msg: string): void
|
|
145
|
-
success(msg: string): void
|
|
146
|
-
plugin(name: string, msg: string): void
|
|
147
|
-
step(step: any): void
|
|
148
|
-
suite: {
|
|
149
|
-
started: Function
|
|
150
|
-
}
|
|
151
|
-
test: {
|
|
152
|
-
started(test: string): void
|
|
153
|
-
passed(test: string): void
|
|
154
|
-
failed(test: string): void
|
|
155
|
-
skipped(test: string): void
|
|
156
|
-
}
|
|
157
|
-
scenario: {
|
|
158
|
-
started(test: string): void
|
|
159
|
-
passed(test: string): void
|
|
160
|
-
failed(test: string): void
|
|
161
|
-
}
|
|
162
|
-
say(message: string, color?: string): void
|
|
163
|
-
result(passed: number, failed: number, skipped: number, duration: string): void
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
declare class Config {
|
|
167
|
-
create(newConfig: Object): Object
|
|
168
|
-
load(configFile: string): Config
|
|
169
|
-
get(key: string, val: any): any
|
|
170
|
-
append(additionalConfig: Object): Object
|
|
171
|
-
reset(): Object
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
declare class Helper {
|
|
175
|
-
/** Abstract method to provide required config options */
|
|
176
|
-
static _config(): any;
|
|
177
|
-
/** Abstract method to validate config */
|
|
178
|
-
_validateConfig<T>(config: T): T;
|
|
179
|
-
/** Sets config for current test */
|
|
180
|
-
_setConfig(opts: any): void;
|
|
181
|
-
/** Hook executed before all tests */
|
|
182
|
-
_init(): void
|
|
183
|
-
/** Hook executed before each test. */
|
|
184
|
-
_before(): void
|
|
185
|
-
/** Hook executed after each test */
|
|
186
|
-
_after(): void
|
|
187
|
-
/**
|
|
188
|
-
* Hook provides a test details
|
|
189
|
-
* Executed in the very beginning of a test
|
|
190
|
-
*/
|
|
191
|
-
_test(test: () => void): void
|
|
192
|
-
/** Hook executed after each passed test */
|
|
193
|
-
_passed(test: () => void): void
|
|
194
|
-
/** Hook executed after each failed test */
|
|
195
|
-
_failed(test: () => void): void
|
|
196
|
-
/** Hook executed before each step */
|
|
197
|
-
_beforeStep(step: () => void): void
|
|
198
|
-
/** Hook executed after each step */
|
|
199
|
-
_afterStep(step: () => void): void
|
|
200
|
-
/** Hook executed before each suite */
|
|
201
|
-
_beforeSuite(suite: () => void): void
|
|
202
|
-
/** Hook executed after each suite */
|
|
203
|
-
_afterSuite(suite: () => void): void
|
|
204
|
-
/** Hook executed after all tests are executed */
|
|
205
|
-
_finishTest(suite: () => void): void
|
|
206
|
-
/**Access another configured helper: this.helpers['AnotherHelper'] */
|
|
207
|
-
readonly helpers: any
|
|
208
|
-
/** Print debug message to console (outputs only in debug mode) */
|
|
209
|
-
debug(msg: string): void
|
|
210
9
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
constructor(locator: LocatorOrString, defaultType?: string);
|
|
216
|
-
|
|
217
|
-
or(locator: LocatorOrString): Locator;
|
|
218
|
-
find(locator: LocatorOrString): Locator;
|
|
219
|
-
withChild(locator: LocatorOrString): Locator;
|
|
220
|
-
withDescendant(locator: LocatorOrString): Locator;
|
|
221
|
-
at(position: number): Locator;
|
|
222
|
-
first(): Locator;
|
|
223
|
-
last(): Locator;
|
|
224
|
-
inside(locator: LocatorOrString): Locator;
|
|
225
|
-
before(locator: LocatorOrString): Locator;
|
|
226
|
-
after(locator: LocatorOrString): Locator;
|
|
227
|
-
withText(text: string): Locator;
|
|
228
|
-
withAttr(attrs: object): Locator;
|
|
229
|
-
as(output: string): Locator;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
declare function actor(customSteps?: {
|
|
234
|
-
[action: string]: (this: CodeceptJS.I, ...args: any[]) => void
|
|
235
|
-
}): CodeceptJS.{{I}};
|
|
236
|
-
declare function actor(customSteps?: {}): CodeceptJS.{{I}};
|
|
237
|
-
declare function Feature(title: string, opts?: {}): FeatureConfig;
|
|
238
|
-
declare const Scenario: {
|
|
239
|
-
(title: string, callback: ICodeceptCallback): ScenarioConfig;
|
|
240
|
-
(title: string, opts: {}, callback: ICodeceptCallback): ScenarioConfig;
|
|
241
|
-
only(title: string, callback: ICodeceptCallback): ScenarioConfig;
|
|
242
|
-
only(title: string, opts: {}, callback: ICodeceptCallback): ScenarioConfig;
|
|
243
|
-
}
|
|
244
|
-
declare interface IScenario {
|
|
245
|
-
Scenario(title: string, callback: ICodeceptCallback): ScenarioConfig;
|
|
246
|
-
Scenario(title: string, opts: {}, callback: ICodeceptCallback): ScenarioConfig;
|
|
247
|
-
}
|
|
248
|
-
declare function xScenario(title: string, callback: ICodeceptCallback): ScenarioConfig;
|
|
249
|
-
declare function xScenario(title: string, opts: {}, callback: ICodeceptCallback): ScenarioConfig;
|
|
250
|
-
declare interface IData {
|
|
251
|
-
Scenario(title: string, callback: ICodeceptCallback): ScenarioConfig;
|
|
252
|
-
Scenario(title: string, opts: {}, callback: ICodeceptCallback): ScenarioConfig;
|
|
253
|
-
only: IScenario;
|
|
254
|
-
}
|
|
255
|
-
declare function Data(data: any): IData;
|
|
256
|
-
declare function xData(data: any): IData;
|
|
257
|
-
declare function Before(callback: ICodeceptCallback): void;
|
|
258
|
-
declare function BeforeSuite(callback: ICodeceptCallback): void;
|
|
259
|
-
declare function After(callback: ICodeceptCallback): void;
|
|
260
|
-
declare function AfterSuite(callback: ICodeceptCallback): void;
|
|
261
|
-
|
|
262
|
-
declare function inject(): {
|
|
263
|
-
{{injectPageObjects}}
|
|
264
|
-
};
|
|
265
|
-
declare function locate(selector: LocatorOrString): Locator;
|
|
266
|
-
declare function within(selector: LocatorOrString, callback: Function): Promise<any>;
|
|
267
|
-
declare function session(selector: LocatorOrString, callback: Function): Promise<any>;
|
|
268
|
-
declare function session(selector: LocatorOrString, config: any, callback: Function): Promise<any>;
|
|
269
|
-
declare function pause(): void;
|
|
270
|
-
declare function secret(secret: any): string;
|
|
271
|
-
|
|
272
|
-
declare const codeceptjs: any;
|
|
10
|
+
const template = ({
|
|
11
|
+
helperNames, supportObject, importPaths, translations, hasCustomStepsFile,
|
|
12
|
+
}) => `/// <reference types='codeceptjs' />
|
|
13
|
+
${importPaths.join('\n')}
|
|
273
14
|
|
|
274
15
|
declare namespace CodeceptJS {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
export interface {{I}} {
|
|
282
|
-
{{methods}}
|
|
16
|
+
interface SupportObject ${convertMapToType(supportObject)}
|
|
17
|
+
interface CallbackOrder { ${convertMapToIndexedInterface(supportObject)} }
|
|
18
|
+
${helperNames.length > 0 ? `interface Methods extends ${helperNames.join(', ')} {}` : ''}
|
|
19
|
+
interface I extends ${hasCustomStepsFile ? 'ReturnType<steps_file>' : 'WithTranslation<Methods>'} {}
|
|
20
|
+
namespace Translation {
|
|
21
|
+
interface Actions ${JSON.stringify(translations.vocabulary.actions, null, 2)}
|
|
283
22
|
}
|
|
284
|
-
{{exportPageObjects}}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
declare module "codeceptjs" {
|
|
288
|
-
export = CodeceptJS;
|
|
289
23
|
}
|
|
290
24
|
`;
|
|
291
25
|
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
const pageObjectTemplate = `
|
|
295
|
-
export interface {{name}} {
|
|
296
|
-
{{methods}}
|
|
297
|
-
}
|
|
298
|
-
`;
|
|
26
|
+
const helperNames = [];
|
|
299
27
|
|
|
300
28
|
module.exports = function (genPath, options) {
|
|
301
29
|
const configFile = options.config || genPath;
|
|
@@ -303,89 +31,109 @@ module.exports = function (genPath, options) {
|
|
|
303
31
|
const config = getConfig(configFile);
|
|
304
32
|
if (!config) return;
|
|
305
33
|
|
|
34
|
+
/** @type {Object<string, string>} */
|
|
35
|
+
const helperPaths = {};
|
|
36
|
+
/** @type {Object<string, string>} */
|
|
37
|
+
const supportPaths = {};
|
|
38
|
+
let hasCustomStepsFile = false;
|
|
39
|
+
|
|
306
40
|
const targetFolderPath = options.output && getTestRoot(options.output) || testsPath;
|
|
307
41
|
|
|
308
42
|
const codecept = new Codecept(config, {});
|
|
309
43
|
codecept.init(testsPath);
|
|
310
44
|
|
|
311
45
|
const helpers = container.helpers();
|
|
312
|
-
const suppportI = container.support('I');
|
|
313
46
|
const translations = container.translation();
|
|
314
|
-
let methods = [];
|
|
315
|
-
const actions = [];
|
|
316
47
|
for (const name in helpers) {
|
|
317
|
-
const
|
|
318
|
-
|
|
48
|
+
const require = codecept.config.helpers[name].require;
|
|
49
|
+
if (require) {
|
|
50
|
+
helperPaths[name] = require;
|
|
51
|
+
helperNames.push(name);
|
|
52
|
+
} else {
|
|
53
|
+
helperNames.push(`CodeceptJS.${name}`);
|
|
54
|
+
}
|
|
319
55
|
}
|
|
320
|
-
methods = addAllNamesInObject(suppportI, actions, methods);
|
|
321
56
|
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (name === 'I') {
|
|
331
|
-
return;
|
|
57
|
+
const supportObject = new Map();
|
|
58
|
+
supportObject.set('I', 'CodeceptJS.I');
|
|
59
|
+
for (const name in codecept.config.include) {
|
|
60
|
+
const includePath = codecept.config.include[name];
|
|
61
|
+
if (name === 'I' || name === translations.I) {
|
|
62
|
+
hasCustomStepsFile = true;
|
|
63
|
+
supportPaths.steps_file = includePath;
|
|
64
|
+
continue;
|
|
332
65
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const pageMethods = addAllMethodsInObject(pageObject, {}, []);
|
|
336
|
-
let pageObjectExport = pageObjectTemplate.replace('{{methods}}', pageMethods.join(''));
|
|
337
|
-
pageObjectExport = pageObjectExport.replace('{{name}}', String(name));
|
|
338
|
-
injectPageObjects.push(injectSupportTemplate.replace(/{{name}}/g, String(name)));
|
|
339
|
-
exportPageObjects.push(pageObjectExport);
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
let definitionsTemplate = template.replace('{{methods}}', methods.join(''));
|
|
343
|
-
definitionsTemplate = definitionsTemplate.replace('{{exportPageObjects}}', exportPageObjects.join('\n'));
|
|
344
|
-
definitionsTemplate = definitionsTemplate.replace('{{injectPageObjects}}', injectPageObjects.join('\n'));
|
|
345
|
-
definitionsTemplate = definitionsTemplate.replace('{{callbackParams}}', `, ${[...callbackParams, '...args: any'].join(', ')}`);
|
|
346
|
-
if (translations) {
|
|
347
|
-
definitionsTemplate = definitionsTemplate.replace(/\{\{I\}\}/g, translations.I);
|
|
66
|
+
supportPaths[name] = includePath;
|
|
67
|
+
supportObject.set(name, name);
|
|
348
68
|
}
|
|
349
69
|
|
|
70
|
+
const definitionsTemplate = template({
|
|
71
|
+
helperNames,
|
|
72
|
+
supportObject,
|
|
73
|
+
importPaths: getImportString(testsPath, targetFolderPath, supportPaths, helperPaths),
|
|
74
|
+
translations,
|
|
75
|
+
hasCustomStepsFile,
|
|
76
|
+
});
|
|
77
|
+
|
|
350
78
|
fs.writeFileSync(path.join(targetFolderPath, 'steps.d.ts'), definitionsTemplate);
|
|
351
79
|
output.print('TypeScript Definitions provide autocompletion in Visual Studio Code and other IDEs');
|
|
352
80
|
output.print('Definitions were generated in steps.d.ts');
|
|
353
81
|
};
|
|
354
82
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Returns the relative path from the to the targeted folder.
|
|
85
|
+
* @param {string} originalPath
|
|
86
|
+
* @param { string} targetFolderPath
|
|
87
|
+
* @param { string} testsPath
|
|
88
|
+
*/
|
|
89
|
+
function getPath(originalPath, targetFolderPath, testsPath) {
|
|
90
|
+
const parsedPath = path.parse(originalPath);
|
|
91
|
+
|
|
92
|
+
// Remove typescript extension if exists.
|
|
93
|
+
if (parsedPath.base.endsWith('.d.ts')) parsedPath.base = parsedPath.base.substring(0, parsedPath.base.length - 5);
|
|
94
|
+
else if (parsedPath.ext === '.ts') parsedPath.base = parsedPath.name;
|
|
95
|
+
|
|
96
|
+
if (!parsedPath.dir.startsWith('.')) return path.join(parsedPath.dir, parsedPath.base);
|
|
97
|
+
const relativePath = path.relative(targetFolderPath, path.join(testsPath, parsedPath.dir, parsedPath.base));
|
|
98
|
+
|
|
99
|
+
return relativePath.startsWith('.') ? relativePath : `./${relativePath}`;
|
|
368
100
|
}
|
|
369
101
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
} else {
|
|
386
|
-
methods.push(` ${(name)}: (${params}) => any; \n`);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
102
|
+
/**
|
|
103
|
+
*
|
|
104
|
+
*
|
|
105
|
+
* @param {string} testsPath
|
|
106
|
+
* @param {string} targetFolderPath
|
|
107
|
+
* @param {Object<string, string>} pathsToType
|
|
108
|
+
* @param {Object<string, string>} pathsToValue
|
|
109
|
+
* @returns
|
|
110
|
+
*/
|
|
111
|
+
function getImportString(testsPath, targetFolderPath, pathsToType, pathsToValue) {
|
|
112
|
+
const importStrings = [];
|
|
113
|
+
|
|
114
|
+
for (const name in pathsToType) {
|
|
115
|
+
const relativePath = getPath(pathsToType[name], targetFolderPath, testsPath);
|
|
116
|
+
importStrings.push(`type ${name} = typeof import('${relativePath}');`);
|
|
389
117
|
}
|
|
390
|
-
|
|
118
|
+
|
|
119
|
+
for (const name in pathsToValue) {
|
|
120
|
+
const relativePath = getPath(pathsToValue[name], targetFolderPath, testsPath);
|
|
121
|
+
importStrings.push(`type ${name} = import('${relativePath}');`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return importStrings;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @param {Map} map
|
|
129
|
+
*/
|
|
130
|
+
function convertMapToIndexedInterface(map) {
|
|
131
|
+
return [...map.values()].map((value, i) => `[${i}]: ${value}`).join('; ');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @param {Map} map
|
|
136
|
+
*/
|
|
137
|
+
function convertMapToType(map) {
|
|
138
|
+
return `{ ${Array.from(map).map(([key, value]) => `${key}: ${value}`).join(', ')} }`;
|
|
391
139
|
}
|
package/lib/command/generate.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
const
|
|
2
|
-
const inquirer = require('inquirer');
|
|
1
|
+
const colors = require('chalk');
|
|
3
2
|
const fs = require('fs');
|
|
3
|
+
const inquirer = require('inquirer');
|
|
4
|
+
const mkdirp = require('mkdirp');
|
|
4
5
|
const path = require('path');
|
|
5
|
-
|
|
6
|
+
|
|
6
7
|
const { fileExists, ucfirst, lcfirst } = require('../utils');
|
|
7
|
-
const
|
|
8
|
+
const output = require('../output');
|
|
8
9
|
const {
|
|
9
|
-
getConfig, getTestRoot,
|
|
10
|
+
getConfig, getTestRoot, safeFileWrite,
|
|
10
11
|
} = require('./utils');
|
|
11
12
|
|
|
12
|
-
const testTemplate = `
|
|
13
|
-
Feature('{{feature}}');
|
|
13
|
+
const testTemplate = `Feature('{{feature}}');
|
|
14
14
|
|
|
15
15
|
Scenario('test something', ({{actor}}) => {
|
|
16
16
|
|
|
@@ -28,18 +28,18 @@ module.exports.test = function (genPath) {
|
|
|
28
28
|
|
|
29
29
|
const defaultExt = config.tests.match(/([^\*/]*?)$/[1])[0] || '_test.js';
|
|
30
30
|
|
|
31
|
-
inquirer.prompt([
|
|
31
|
+
return inquirer.prompt([
|
|
32
32
|
{
|
|
33
33
|
type: 'input',
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
name: 'feature',
|
|
35
|
+
message: 'Feature which is being tested',
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
type: 'input',
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
message: 'Filename of a test',
|
|
40
|
+
name: 'filename',
|
|
41
41
|
default(answers) {
|
|
42
|
-
return
|
|
42
|
+
return (answers.feature).replace(' ', '_').toLowerCase() + defaultExt;
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
45
|
]).then((result) => {
|
|
@@ -81,7 +81,7 @@ module.exports.pageObject = function (genPath, opts) {
|
|
|
81
81
|
output.print(`Creating a new ${kind} object`);
|
|
82
82
|
output.print('--------------------------');
|
|
83
83
|
|
|
84
|
-
inquirer.prompt([{
|
|
84
|
+
return inquirer.prompt([{
|
|
85
85
|
type: 'input',
|
|
86
86
|
name: 'name',
|
|
87
87
|
message: `Name of a ${kind} object`,
|
|
@@ -118,16 +118,21 @@ module.exports.pageObject = function (genPath, opts) {
|
|
|
118
118
|
});
|
|
119
119
|
};
|
|
120
120
|
|
|
121
|
-
const helperTemplate = `
|
|
122
|
-
const Helper = codeceptjs.helper;
|
|
121
|
+
const helperTemplate = `const { Helper } = codeceptjs;
|
|
123
122
|
|
|
124
123
|
class {{name}} extends Helper {
|
|
125
124
|
|
|
126
125
|
// before/after hooks
|
|
126
|
+
/**
|
|
127
|
+
* @protected
|
|
128
|
+
*/
|
|
127
129
|
_before() {
|
|
128
130
|
// remove if not used
|
|
129
131
|
}
|
|
130
132
|
|
|
133
|
+
/**
|
|
134
|
+
* @protected
|
|
135
|
+
*/
|
|
131
136
|
_after() {
|
|
132
137
|
// remove if not used
|
|
133
138
|
}
|
|
@@ -148,7 +153,7 @@ module.exports.helper = function (genPath) {
|
|
|
148
153
|
output.print('Creating a new helper');
|
|
149
154
|
output.print('--------------------------');
|
|
150
155
|
|
|
151
|
-
inquirer.prompt([{
|
|
156
|
+
return inquirer.prompt([{
|
|
152
157
|
type: 'input',
|
|
153
158
|
name: 'name',
|
|
154
159
|
message: 'Name of a Helper',
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
const output = require('../../output');
|
|
2
1
|
const inquirer = require('inquirer');
|
|
3
2
|
const fs = require('fs');
|
|
4
3
|
const path = require('path');
|
|
5
4
|
const mkdirp = require('mkdirp');
|
|
5
|
+
|
|
6
|
+
const output = require('../../output');
|
|
6
7
|
const { fileExists, ucfirst, lcfirst } = require('../../utils');
|
|
7
8
|
const {
|
|
8
9
|
getConfig, getTestRoot, updateConfig, safeFileWrite,
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
+
const escapeStringRegexp = require('escape-string-regexp');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const { Parser } = require('gherkin');
|
|
4
|
+
const glob = require('glob');
|
|
5
|
+
const fsPath = require('path');
|
|
6
|
+
|
|
1
7
|
const getConfig = require('../utils').getConfig;
|
|
2
8
|
const getTestRoot = require('../utils').getTestRoot;
|
|
3
9
|
const Codecept = require('../../codecept');
|
|
4
|
-
const container = require('../../container');
|
|
5
10
|
const output = require('../../output');
|
|
6
11
|
const { matchStep } = require('../../interfaces/bdd');
|
|
7
|
-
const { Parser } = require('gherkin');
|
|
8
|
-
const glob = require('glob');
|
|
9
|
-
const fsPath = require('path');
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const escapeStringRegexp = require('escape-string-regexp');
|
|
12
12
|
|
|
13
13
|
const parser = new Parser();
|
|
14
14
|
parser.stopAtFirstError = false;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const envinfo = require('envinfo');
|
|
2
|
+
|
|
3
|
+
const getConfig = require('./utils').getConfig;
|
|
4
|
+
const getTestRoot = require('./utils').getTestRoot;
|
|
5
|
+
const Codecept = require('../codecept');
|
|
6
|
+
const output = require('../output');
|
|
7
|
+
|
|
8
|
+
module.exports = async function (path) {
|
|
9
|
+
const testsPath = getTestRoot(path);
|
|
10
|
+
const config = getConfig(testsPath);
|
|
11
|
+
const codecept = new Codecept(config, {});
|
|
12
|
+
codecept.init(testsPath);
|
|
13
|
+
|
|
14
|
+
output.print('\n Environment information:-\n');
|
|
15
|
+
const info = {};
|
|
16
|
+
info.codeceptVersion = Codecept.version();
|
|
17
|
+
info.nodeInfo = await envinfo.helpers.getNodeInfo();
|
|
18
|
+
info.osInfo = await envinfo.helpers.getOSInfo();
|
|
19
|
+
info.cpuInfo = await envinfo.helpers.getCPUInfo();
|
|
20
|
+
info.chromeInfo = await envinfo.helpers.getChromeInfo();
|
|
21
|
+
info.edgeInfo = await envinfo.helpers.getEdgeInfo();
|
|
22
|
+
info.firefoxInfo = await envinfo.helpers.getFirefoxInfo();
|
|
23
|
+
info.safariInfo = await envinfo.helpers.getSafariInfo();
|
|
24
|
+
const { helpers, plugins } = config;
|
|
25
|
+
info.helpers = helpers || "You don't use any helpers";
|
|
26
|
+
info.plugins = plugins || "You don't have any enabled plugins";
|
|
27
|
+
|
|
28
|
+
for (const [key, value] of Object.entries(info)) {
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
output.print(`${key}: ${value[1]}`);
|
|
31
|
+
} else {
|
|
32
|
+
output.print(`${key}: ${JSON.stringify(value, null, ' ')}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
output.print('***************************************');
|
|
36
|
+
output.print('If you have questions ask them in our Slack: shorturl.at/cuKU8');
|
|
37
|
+
output.print('Or ask them on our discussion board: https://codecept.discourse.group/');
|
|
38
|
+
output.print('Please copy environment info when you report issues on GitHub: https://github.com/Codeception/CodeceptJS/issues');
|
|
39
|
+
output.print('***************************************');
|
|
40
|
+
};
|