codeceptjs 2.1.3 → 2.2.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.
Files changed (173) hide show
  1. package/CHANGELOG.md +125 -37
  2. package/README.md +15 -22
  3. package/bin/codecept.js +4 -1
  4. package/docs/acceptance.md +44 -1
  5. package/docs/advanced.md +1 -1
  6. package/docs/angular.md +6 -9
  7. package/docs/basics.md +388 -75
  8. package/docs/bdd.md +4 -3
  9. package/docs/best.md +1 -1
  10. package/docs/books.md +31 -0
  11. package/docs/build/Appium.js +215 -176
  12. package/docs/build/Nightmare.js +618 -489
  13. package/docs/build/Polly.js +189 -0
  14. package/docs/build/Protractor.js +747 -608
  15. package/docs/build/Puppeteer.js +914 -633
  16. package/docs/build/REST.js +1 -1
  17. package/docs/build/TestCafe.js +1835 -0
  18. package/docs/build/WebDriver.js +861 -805
  19. package/docs/build/WebDriverIO.js +616 -617
  20. package/docs/changelog.md +410 -316
  21. package/docs/commands.md +6 -6
  22. package/docs/community-helpers.md +2 -0
  23. package/docs/detox.md +235 -0
  24. package/docs/examples.md +23 -0
  25. package/docs/helpers/ApiDataFactory.md +11 -10
  26. package/docs/helpers/Appium.md +130 -61
  27. package/docs/helpers/Detox.md +579 -0
  28. package/docs/helpers/FileSystem.md +2 -1
  29. package/docs/helpers/Mochawesome.md +1 -0
  30. package/docs/helpers/Nightmare.md +348 -128
  31. package/docs/helpers/Polly.md +85 -0
  32. package/docs/helpers/Protractor.md +451 -184
  33. package/docs/helpers/Puppeteer-firefox.md +55 -0
  34. package/docs/helpers/Puppeteer.md +619 -183
  35. package/docs/helpers/REST.md +17 -16
  36. package/docs/helpers/SeleniumWebdriver.md +9 -8
  37. package/docs/helpers/TestCafe.md +1168 -0
  38. package/docs/helpers/WebDriver.md +600 -291
  39. package/docs/helpers/WebDriverIO.md +393 -278
  40. package/docs/helpers.md +37 -18
  41. package/docs/locators.md +2 -0
  42. package/docs/mobile-react-native-locators.md +64 -0
  43. package/docs/mobile.md +5 -0
  44. package/docs/plugins.md +54 -13
  45. package/docs/puppeteer.md +74 -26
  46. package/docs/quickstart.md +47 -12
  47. package/docs/react.md +67 -0
  48. package/docs/reports.md +1 -1
  49. package/docs/{webapi/_keys.mustache → shared/keys.mustache} +0 -0
  50. package/docs/shared/react.mustache +1 -0
  51. package/docs/testcafe.md +157 -0
  52. package/docs/videos.md +19 -0
  53. package/docs/webapi/amOnPage.mustache +1 -1
  54. package/docs/webapi/appendField.mustache +2 -2
  55. package/docs/webapi/attachFile.mustache +2 -2
  56. package/docs/webapi/checkOption.mustache +2 -2
  57. package/docs/webapi/clearCookie.mustache +1 -1
  58. package/docs/webapi/clearField.mustache +1 -1
  59. package/docs/webapi/click.mustache +2 -2
  60. package/docs/webapi/clickLink.mustache +3 -3
  61. package/docs/webapi/dontSee.mustache +6 -3
  62. package/docs/webapi/dontSeeCheckboxIsChecked.mustache +7 -1
  63. package/docs/webapi/dontSeeCookie.mustache +5 -1
  64. package/docs/webapi/dontSeeCurrentUrlEquals.mustache +6 -1
  65. package/docs/webapi/dontSeeElement.mustache +5 -1
  66. package/docs/webapi/dontSeeElementInDOM.mustache +5 -1
  67. package/docs/webapi/dontSeeInCurrentUrl.mustache +1 -1
  68. package/docs/webapi/dontSeeInField.mustache +7 -2
  69. package/docs/webapi/dontSeeInSource.mustache +5 -1
  70. package/docs/webapi/dontSeeInTitle.mustache +5 -1
  71. package/docs/webapi/doubleClick.mustache +2 -2
  72. package/docs/webapi/downloadFile.mustache +2 -2
  73. package/docs/webapi/dragAndDrop.mustache +2 -2
  74. package/docs/webapi/dragSlider.mustache +2 -2
  75. package/docs/webapi/executeAsyncScript.mustache +1 -1
  76. package/docs/webapi/executeScript.mustache +1 -1
  77. package/docs/webapi/fillField.mustache +2 -2
  78. package/docs/webapi/grabAttributeFrom.mustache +3 -2
  79. package/docs/webapi/grabBrowserLogs.mustache +3 -1
  80. package/docs/webapi/grabCookie.mustache +2 -1
  81. package/docs/webapi/grabCssPropertyFrom.mustache +3 -2
  82. package/docs/webapi/grabCurrentUrl.mustache +3 -1
  83. package/docs/webapi/grabDataFromPerformanceTiming.mustache +19 -0
  84. package/docs/webapi/grabHTMLFrom.mustache +2 -1
  85. package/docs/webapi/grabNumberOfOpenTabs.mustache +4 -2
  86. package/docs/webapi/grabNumberOfVisibleElements.mustache +3 -2
  87. package/docs/webapi/grabPageScrollPosition.mustache +3 -1
  88. package/docs/webapi/grabSource.mustache +3 -1
  89. package/docs/webapi/grabTextFrom.mustache +2 -1
  90. package/docs/webapi/grabTitle.mustache +3 -1
  91. package/docs/webapi/grabValueFrom.mustache +2 -1
  92. package/docs/webapi/moveCursorTo.mustache +3 -3
  93. package/docs/webapi/pressKey.mustache +1 -1
  94. package/docs/webapi/resizeWindow.mustache +2 -2
  95. package/docs/webapi/rightClick.mustache +2 -2
  96. package/docs/webapi/saveScreenshot.mustache +3 -3
  97. package/docs/webapi/say.mustache +2 -2
  98. package/docs/webapi/scrollPageToBottom.mustache +1 -1
  99. package/docs/webapi/scrollPageToTop.mustache +1 -1
  100. package/docs/webapi/scrollTo.mustache +3 -3
  101. package/docs/webapi/see.mustache +2 -2
  102. package/docs/webapi/seeAttributesOnElements.mustache +3 -3
  103. package/docs/webapi/seeCheckboxIsChecked.mustache +2 -1
  104. package/docs/webapi/seeCookie.mustache +1 -1
  105. package/docs/webapi/seeCssPropertiesOnElements.mustache +2 -2
  106. package/docs/webapi/seeCurrentUrlEquals.mustache +1 -1
  107. package/docs/webapi/seeElement.mustache +1 -1
  108. package/docs/webapi/seeElementInDOM.mustache +1 -1
  109. package/docs/webapi/seeInCurrentUrl.mustache +1 -1
  110. package/docs/webapi/seeInField.mustache +2 -2
  111. package/docs/webapi/seeInSource.mustache +1 -1
  112. package/docs/webapi/seeInTitle.mustache +5 -1
  113. package/docs/webapi/seeNumberOfElements.mustache +10 -0
  114. package/docs/webapi/seeNumberOfVisibleElements.mustache +2 -2
  115. package/docs/webapi/selectOption.mustache +2 -2
  116. package/docs/webapi/setCookie.mustache +1 -1
  117. package/docs/webapi/switchTo.mustache +6 -1
  118. package/docs/webapi/uncheckOption.mustache +2 -2
  119. package/docs/webapi/wait.mustache +1 -2
  120. package/docs/webapi/waitForDetached.mustache +3 -3
  121. package/docs/webapi/waitForElement.mustache +2 -2
  122. package/docs/webapi/waitForEnabled.mustache +1 -1
  123. package/docs/webapi/waitForFunction.mustache +3 -3
  124. package/docs/webapi/waitForInvisible.mustache +3 -3
  125. package/docs/webapi/waitForText.mustache +3 -3
  126. package/docs/webapi/waitForValue.mustache +3 -3
  127. package/docs/webapi/waitForVisible.mustache +3 -3
  128. package/docs/webapi/waitInUrl.mustache +2 -2
  129. package/docs/webapi/waitNumberOfVisibleElements.mustache +3 -3
  130. package/docs/webapi/waitToHide.mustache +3 -3
  131. package/docs/webapi/waitUntil.mustache +3 -3
  132. package/docs/webapi/waitUrlEquals.mustache +2 -2
  133. package/docs/webdriver.md +453 -0
  134. package/lib/codecept.js +11 -9
  135. package/lib/command/definitions.js +183 -30
  136. package/lib/command/gherkin/snippets.js +29 -9
  137. package/lib/command/init.js +31 -9
  138. package/lib/command/run-multiple.js +46 -59
  139. package/lib/command/utils.js +1 -1
  140. package/lib/container.js +30 -4
  141. package/lib/data/dataScenarioConfig.js +18 -0
  142. package/lib/helper/Appium.js +24 -24
  143. package/lib/helper/Nightmare.js +81 -84
  144. package/lib/helper/Polly.js +189 -0
  145. package/lib/helper/Protractor.js +96 -86
  146. package/lib/helper/Puppeteer.js +238 -113
  147. package/lib/helper/REST.js +1 -1
  148. package/lib/helper/TestCafe.js +1257 -0
  149. package/lib/helper/WebDriver.js +217 -277
  150. package/lib/helper/WebDriverIO.js +75 -75
  151. package/lib/helper/clientscripts/nightmare.js +8 -0
  152. package/lib/helper/extras/React.js +55 -0
  153. package/lib/helper/testcafe/testControllerHolder.js +42 -0
  154. package/lib/helper/testcafe/testcafe-utils.js +63 -0
  155. package/lib/history.js +39 -0
  156. package/lib/hooks.js +25 -1
  157. package/lib/interfaces/gherkin.js +17 -1
  158. package/lib/interfaces/scenarioConfig.js +2 -2
  159. package/lib/listener/config.js +3 -3
  160. package/lib/locator.js +6 -0
  161. package/lib/pause.js +22 -1
  162. package/lib/plugin/allure.js +63 -0
  163. package/lib/plugin/autoLogin.js +65 -16
  164. package/lib/plugin/puppeteerCoverage.js +6 -1
  165. package/lib/plugin/stepByStepReport.js +4 -3
  166. package/lib/scenario.js +23 -17
  167. package/lib/step.js +5 -2
  168. package/lib/ui.js +1 -1
  169. package/lib/utils.js +70 -20
  170. package/package.json +20 -19
  171. package/translations/de-DE.js +69 -0
  172. package/translations/index.js +1 -0
  173. package/docs/video.md +0 -26
@@ -9,23 +9,24 @@ const fs = require('fs');
9
9
  const path = require('path');
10
10
 
11
11
  const template = `
12
- type ICodeceptCallback = (i: CodeceptJS.{{I}}{{callbackParams}}) => void;
12
+ type ICodeceptCallback = (i?: CodeceptJS.{{I}}, current?:any{{callbackParams}}) => void;
13
13
 
14
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
15
+ retry(times: number): FeatureConfig
16
+ timeout(seconds: number): FeatureConfig
17
+ config(config: object): FeatureConfig
18
+ config(helperName: string, config: object): FeatureConfig
19
19
  }
20
20
 
21
21
  declare class ScenarioConfig {
22
- throws(err:any) : ScenarioConfig;
23
- fails() : ScenarioConfig;
24
- retry(times:number): ScenarioConfig
25
- timeout(timeout:number): ScenarioConfig
26
- inject(inject:object): ScenarioConfig
27
- config(config:object): ScenarioConfig
28
- config(helperName:string, config:object): ScenarioConfig
22
+ throws(err: any): ScenarioConfig;
23
+ fails(): ScenarioConfig;
24
+ retry(times: number): ScenarioConfig
25
+ timeout(timeout: number): ScenarioConfig
26
+ inject(inject: object): ScenarioConfig
27
+ config(config: object): ScenarioConfig
28
+ config(helperName: string, config: object): ScenarioConfig
29
+ injectDependencies(dependencies: { [key: string]: any }): ScenarioConfig
29
30
  }
30
31
 
31
32
  interface ILocator {
@@ -40,6 +41,134 @@ interface ILocator {
40
41
 
41
42
  type LocatorOrString = string | ILocator | Locator;
42
43
 
44
+ declare class Container {
45
+ create(config: Object, opts: Object): void
46
+ plugins(name?: string): Object
47
+ support(name?: string): Object
48
+ helpers(name?: string): Object
49
+ translation(): Object
50
+ mocha(): Object
51
+ append(newContainer: Object): void
52
+ clear(newHelpers: Object, newSupport: Object, newPlugins: Object): void
53
+ }
54
+
55
+ declare class RecorderSession {
56
+ running: boolean
57
+ start(name: string): void
58
+ restore(name: string): void
59
+ catch(fn: CallableFunction): void
60
+ }
61
+
62
+ declare class Recorder {
63
+ retries: Object[]
64
+ start(): void
65
+ isRunning(): boolean
66
+ startUnlessRunning(): void
67
+ errHandler(fn: CallableFunction): void
68
+ reset(): void
69
+ session: RecorderSession
70
+ add(taskName, fn?: CallableFunction, force?: boolean, retry?: boolean): Promise<any>
71
+ retry(opts: Object): Promise<any>
72
+ catch(customErrFn: CallableFunction): Promise<any>
73
+ catchWithoutStop(customErrFn: CallableFunction ): Promise<any>
74
+ throw(err: Error): Promise<any>
75
+ saveFirstAsyncError(err: Error): void
76
+ getAsyncErr(): Promise<Error>
77
+ cleanAsyncErr(): void
78
+ stop():void
79
+ promise(): Promise<any>
80
+ scheduled(): string[]
81
+ toString(): string
82
+ add(hookName: string, fn: CallableFunction, force?: boolean): void
83
+ catch(customErrFn: CallableFunction)
84
+ }
85
+
86
+ declare class CodeceptJSEvent {
87
+ dispatcher: EventEmitter
88
+ test: {
89
+ started: string
90
+ before: string
91
+ after: string
92
+ passed: string
93
+ failed: string
94
+ finished: string
95
+ }
96
+ suite: {
97
+ before: string,
98
+ after: string,
99
+ }
100
+ hook: {
101
+ started: string,
102
+ passed: string,
103
+ }
104
+ step: {
105
+ before: string,
106
+ after: string,
107
+ started: string,
108
+ passed: string,
109
+ failed: string,
110
+ finished: string,
111
+ }
112
+ all: {
113
+ before: string,
114
+ after: string,
115
+ result: string,
116
+ }
117
+ multiple: {
118
+ before: string,
119
+ after: string,
120
+ }
121
+ emit(event: string, param: string)
122
+ cleanDispatcher(): void
123
+ }
124
+
125
+ declare class Output {
126
+ colors: any
127
+ styles: {
128
+ error: any,
129
+ success: any,
130
+ scenario: any,
131
+ basic: any,
132
+ debug: any,
133
+ log: any,
134
+ }
135
+
136
+ print(msg: string): void
137
+ stepShift: number
138
+ level(level: number): number
139
+ process(process: string): string
140
+ debug(msg: string): void
141
+ log(msg: string): void
142
+ error(msg: string): void
143
+ success(msg: string): void
144
+ plugin(name: string, msg: string): void
145
+ step(step: any): void
146
+ suite: {
147
+ started: Function
148
+ }
149
+ test: {
150
+ started(test: string): void
151
+ passed(test: string): void
152
+ failed(test: string): void
153
+ skipped(test: string): void
154
+ }
155
+ scenario: {
156
+ started(test: string): void
157
+ passed(test: string): void
158
+ failed(test: string): void
159
+ }
160
+ say(message: string, color?: string): void
161
+ result(passed: number, failed: number, skipped: number, duration: string): void
162
+ }
163
+
164
+ declare class Config {
165
+ create(newConfig: Object): Object
166
+ load(configFile: string): Config
167
+ get(key: string, val: any): any
168
+ append(additionalConfig: Object): Object
169
+ reset(): Object
170
+ }
171
+
43
172
  declare class Helper {
44
173
  /** Abstract method to provide required config options */
45
174
  static _config(): any;
@@ -110,24 +239,43 @@ declare const Scenario: {
110
239
  only(title: string, callback: ICodeceptCallback): ScenarioConfig;
111
240
  only(title: string, opts: {}, callback: ICodeceptCallback): ScenarioConfig;
112
241
  }
242
+ declare interface IScenario {
243
+ Scenario(title: string, callback: ICodeceptCallback): ScenarioConfig;
244
+ Scenario(title: string, opts: {}, callback: ICodeceptCallback): ScenarioConfig;
245
+ }
113
246
  declare function xScenario(title: string, callback: ICodeceptCallback): ScenarioConfig;
114
247
  declare function xScenario(title: string, opts: {}, callback: ICodeceptCallback): ScenarioConfig;
115
- declare function Data(data: any): any;
116
- declare function xData(data: any): any;
248
+ declare interface IData {
249
+ Scenario(title: string, callback: ICodeceptCallback): ScenarioConfig;
250
+ Scenario(title: string, opts: {}, callback: ICodeceptCallback): ScenarioConfig;
251
+ only: IScenario;
252
+ }
253
+ declare function Data(data: any): IData;
254
+ declare function xData(data: any): IData;
117
255
  declare function Before(callback: ICodeceptCallback): void;
118
256
  declare function BeforeSuite(callback: ICodeceptCallback): void;
119
257
  declare function After(callback: ICodeceptCallback): void;
120
258
  declare function AfterSuite(callback: ICodeceptCallback): void;
121
259
 
260
+ declare function inject(): {
261
+ {{injectPageObjects}}
262
+ };
122
263
  declare function locate(selector: LocatorOrString): Locator;
123
264
  declare function within(selector: LocatorOrString, callback: Function): Promise<any>;
124
- declare function session(selector: string, callback: Function): Promise<any>;
125
- declare function session(selector: string, config: any, callback: Function): Promise<any>;
265
+ declare function session(selector: LocatorOrString, callback: Function): Promise<any>;
266
+ declare function session(selector: LocatorOrString, config: any, callback: Function): Promise<any>;
126
267
  declare function pause(): void;
268
+ declare function secret(secret: any): string;
127
269
 
128
270
  declare const codeceptjs: any;
129
271
 
130
272
  declare namespace CodeceptJS {
273
+ export const container: Container
274
+ export const recorder: Recorder
275
+ export const event: CodeceptJSEvent
276
+ export const output: Output
277
+ export const config: Config
278
+
131
279
  export interface {{I}} {
132
280
  {{methods}}
133
281
  }
@@ -135,10 +283,12 @@ declare namespace CodeceptJS {
135
283
  }
136
284
 
137
285
  declare module "codeceptjs" {
138
- export = CodeceptJS;
286
+ export = CodeceptJS;
139
287
  }
140
288
  `;
141
289
 
290
+ const injectSupportTemplate = ' {{name}}: CodeceptJS.{{name}}';
291
+
142
292
  const pageObjectTemplate = `
143
293
  export interface {{name}} {
144
294
  {{methods}}
@@ -151,6 +301,8 @@ module.exports = function (genPath, options) {
151
301
  const config = getConfig(configFile);
152
302
  if (!config) return;
153
303
 
304
+ const targetFolderPath = options.output && getTestRoot(options.output) || testsPath;
305
+
154
306
  const codecept = new Codecept(config, {});
155
307
  codecept.init(testsPath);
156
308
 
@@ -167,33 +319,34 @@ module.exports = function (genPath, options) {
167
319
 
168
320
  const supports = container.support(); // return all support objects
169
321
  const exportPageObjects = [];
322
+ const injectPageObjects = [];
170
323
  const callbackParams = [];
171
- for (const name in supports) {
172
- if (name === 'I') continue;
173
- callbackParams.push(`${name}:CodeceptJS.${name}`);
324
+ // See #1795 and #1799, there's no obvious way to use for-in with Proxies
325
+ Reflect.ownKeys(supports).forEach((name) => {
326
+ if (name === 'I') {
327
+ injectPageObjects.push(injectSupportTemplate.replace(/{{name}}/g, String(name)));
328
+ return;
329
+ }
330
+ callbackParams.push(`${String(name)}?:CodeceptJS.${String(name)}`);
174
331
  const pageObject = supports[name];
175
332
  const pageMethods = addAllMethodsInObject(pageObject, {}, []);
176
333
  let pageObjectExport = pageObjectTemplate.replace('{{methods}}', pageMethods.join(''));
177
- pageObjectExport = pageObjectExport.replace('{{name}}', name);
334
+ pageObjectExport = pageObjectExport.replace('{{name}}', String(name));
335
+ injectPageObjects.push(injectSupportTemplate.replace(/{{name}}/g, String(name)));
178
336
  exportPageObjects.push(pageObjectExport);
179
- }
337
+ });
180
338
 
181
339
  let definitionsTemplate = template.replace('{{methods}}', methods.join(''));
182
340
  definitionsTemplate = definitionsTemplate.replace('{{exportPageObjects}}', exportPageObjects.join('\n'));
183
- if (callbackParams.length > 0) {
184
- definitionsTemplate = definitionsTemplate.replace('{{callbackParams}}', `, ${callbackParams.join(', ')}`);
185
- } else {
186
- definitionsTemplate = definitionsTemplate.replace('{{callbackParams}}', '');
187
- }
341
+ definitionsTemplate = definitionsTemplate.replace('{{injectPageObjects}}', injectPageObjects.join('\n'));
342
+ definitionsTemplate = definitionsTemplate.replace('{{callbackParams}}', `, ${[...callbackParams, '...args: any'].join(', ')}`);
188
343
  if (translations) {
189
344
  definitionsTemplate = definitionsTemplate.replace(/\{\{I\}\}/g, translations.I);
190
345
  }
191
346
 
192
- fs.writeFileSync(path.join(testsPath, 'steps.d.ts'), definitionsTemplate);
347
+ fs.writeFileSync(path.join(targetFolderPath, 'steps.d.ts'), definitionsTemplate);
193
348
  output.print('TypeScript Definitions provide autocompletion in Visual Studio Code and other IDEs');
194
349
  output.print('Definitions were generated in steps.d.ts');
195
- output.print('Load them by adding at the top of a test file:');
196
- output.print(output.colors.grey('\n/// <reference path="./steps.d.ts" />'));
197
350
  };
198
351
 
199
352
  function addAllMethodsInObject(supportObj, actions, methods, translations) {
@@ -8,6 +8,7 @@ const { Parser } = require('gherkin');
8
8
  const glob = require('glob');
9
9
  const fsPath = require('path');
10
10
  const fs = require('fs');
11
+ const escapeStringRegexp = require('escape-string-regexp');
11
12
 
12
13
  const parser = new Parser();
13
14
  parser.stopAtFirstError = false;
@@ -30,13 +31,17 @@ module.exports = function (genPath, options) {
30
31
  output.error('No gherkin steps defined in config. Exiting');
31
32
  process.exit(1);
32
33
  }
33
- if (!config.gherkin.features) {
34
+ if (!options.feature && !config.gherkin.features) {
34
35
  output.error('No gherkin features defined in config. Exiting');
35
36
  process.exit(1);
36
37
  }
38
+ if (options.path && !config.gherkin.steps.includes(options.path)) {
39
+ output.error(`You must include ${options.path} to the gherkin steps in your config file`);
40
+ process.exit(1);
41
+ }
37
42
 
38
43
  const files = [];
39
- glob.sync(config.gherkin.features, { cwd: global.codecept_dir }).forEach((file) => {
44
+ glob.sync(options.feature || config.gherkin.features, { cwd: options.feature ? '.' : global.codecept_dir }).forEach((file) => {
40
45
  if (!fsPath.isAbsolute(file)) {
41
46
  file = fsPath.join(global.codecept_dir, file);
42
47
  }
@@ -58,11 +63,21 @@ module.exports = function (genPath, options) {
58
63
  try {
59
64
  matchStep(step.text);
60
65
  } catch (err) {
61
- let stepLine = step.text
62
- .replace(/\"(.*?)\"/g, '{string}')
63
- .replace(/(\d+\.\d+)/, '{float}')
64
- .replace(/ (\d+) /, ' {int} ');
65
- stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location });
66
+ let stepLine;
67
+ if (/[{}()/]/.test(step.text)) {
68
+ stepLine = escapeStringRegexp(step.text)
69
+ .replace(/\//g, '\\/')
70
+ .replace(/\"(.*?)\"/g, '"(.*?)"')
71
+ .replace(/(\d+\\\.\d+)/, '(\\d+\\.\\d+)')
72
+ .replace(/ (\d+) /, ' (\\d+) ');
73
+ stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: true });
74
+ } else {
75
+ stepLine = step.text
76
+ .replace(/\"(.*?)\"/g, '{string}')
77
+ .replace(/(\d+\.\d+)/, '{float}')
78
+ .replace(/ (\d+) /, ' {int} ');
79
+ stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: false });
80
+ }
66
81
  newSteps.push(stepLine);
67
82
  }
68
83
  }
@@ -81,7 +96,12 @@ module.exports = function (genPath, options) {
81
96
 
82
97
  files.forEach(file => parseFile(file));
83
98
 
84
- let stepFile = config.gherkin.steps[0];
99
+ let stepFile = options.path || config.gherkin.steps[0];
100
+ if (!fs.existsSync(stepFile)) {
101
+ output.error('Please enter a valid step file path ', stepFile);
102
+ process.exit(1);
103
+ }
104
+
85
105
  if (!fsPath.isAbsolute(stepFile)) {
86
106
  stepFile = fsPath.join(global.codecept_dir, stepFile);
87
107
  }
@@ -90,7 +110,7 @@ module.exports = function (genPath, options) {
90
110
  .filter((value, index, self) => self.indexOf(value) === index)
91
111
  .map((step) => {
92
112
  return `
93
- ${step.type}('${step}', () => {
113
+ ${step.type}(${step.regexp ? '/^' : "'"}${step}${step.regexp ? '$/' : "'"}, () => {
94
114
  // From "${step.file}" ${JSON.stringify(step.location)}
95
115
  throw new Error('Not implemented yet');
96
116
  });`;
@@ -4,7 +4,8 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { fileExists, beautify } = require('../utils');
6
6
  const inquirer = require('inquirer');
7
- const getTestRoot = require('./utils').getTestRoot;
7
+ const { getTestRoot } = require('./utils');
8
+ const generateDefinitions = require('./definitions');
8
9
  const isLocal = require('../utils').installedLocally();
9
10
  const mkdirp = require('mkdirp');
10
11
  const { inspect } = require('util');
@@ -18,7 +19,7 @@ const defaultConfig = {
18
19
  mocha: {},
19
20
  };
20
21
 
21
- const helpers = ['WebDriver', 'Protractor', 'Puppeteer', 'Nightmare', 'Appium'];
22
+ const helpers = ['WebDriver', 'Puppeteer', 'TestCafe', 'Protractor', 'Nightmare', 'Appium'];
22
23
  const translations = Object.keys(require('../../translations'));
23
24
 
24
25
  const noTranslation = 'English (no localization)';
@@ -123,7 +124,7 @@ module.exports = function (initPath) {
123
124
  // append file mask to the end of tests
124
125
  if (!config.tests.match(/\*(.*?)$/)) {
125
126
  config.tests = `${config.tests.replace(/\/+$/, '')}/*_test.js`;
126
- console.log(`Adding default test mask: ${config.tests}`);
127
+ console.print(`Adding default test mask: ${config.tests}`);
127
128
  }
128
129
 
129
130
  if (result.translation !== noTranslation) config.translation = result.translation;
@@ -158,28 +159,48 @@ module.exports = function (initPath) {
158
159
  }
159
160
  fs.writeFileSync(stepFile, defaultActor);
160
161
  config.include.I = result.steps_file;
161
- success(`Steps file created at ${stepFile}`);
162
+ print(`Steps file created at ${stepFile}`);
162
163
  }
163
164
 
164
165
  fs.writeFileSync(configFile, beautify(`exports.config = ${inspect(config, false, 4, false)}`), 'utf-8');
165
- success(`Config created at ${configFile}`);
166
+ print(`Config created at ${configFile}`);
166
167
 
167
168
  if (config.output) {
168
169
  if (!fileExists(config.output)) {
169
170
  mkdirp.sync(path.join(testsPath, config.output));
170
- success(`Directory for temporary output files created at '${config.output}'`);
171
+ print(`Directory for temporary output files created at '${config.output}'`);
171
172
  } else {
172
173
  print(`Directory for temporary output files is already created at '${config.output}'`);
173
174
  }
174
175
  }
175
- success('Almost done! Create your first test by executing `npx codeceptjs gt` (generate test) command');
176
+
177
+
178
+ const jsconfig = {
179
+ compilerOption: {
180
+ allowJs: true,
181
+ },
182
+ };
183
+ const jsconfigJson = beautify(JSON.stringify(jsconfig));
184
+ const jsconfigFile = path.join(testsPath, 'jsconfig.json');
185
+ if (fileExists(jsconfigFile)) {
186
+ print(`jsconfig.json has already exists at ${jsconfigFile}`);
187
+ } else {
188
+ fs.writeFileSync(jsconfigFile, jsconfigJson);
189
+ print(`Intellisense enabled in ${jsconfigFile}`);
190
+ }
191
+
192
+ generateDefinitions(testsPath, {});
193
+
194
+ print('');
195
+ success(' Almost done! Next step:');
196
+ success(' Create your first test by executing `npx codeceptjs gt` command ');
176
197
 
177
198
  if (packages) {
178
199
  print('\n--');
179
200
  if (isLocal) {
180
- print(`Please install dependent packages locally: ${colors.bold(`npm install --save-dev ${packages.join(' ')}`)}`);
201
+ success(`Please install dependent packages locally: ${colors.bold(`npm install --save-dev ${packages.join(' ')}`)}`);
181
202
  } else {
182
- print(`Please install dependent packages globally: [sudo] ${colors.bold(`npm install -g ${packages.join(' ')}`)}`);
203
+ success(`Please install dependent packages globally: [sudo] ${colors.bold(`npm install -g ${packages.join(' ')}`)}`);
183
204
  }
184
205
  }
185
206
  };
@@ -194,6 +215,7 @@ module.exports = function (initPath) {
194
215
  config.helpers[helperName][configName] = helperResult[key];
195
216
  });
196
217
 
218
+ print('');
197
219
  finish();
198
220
  });
199
221
  });
@@ -3,10 +3,12 @@ const {
3
3
  } = require('./utils');
4
4
  const fork = require('child_process').fork;
5
5
  const path = require('path');
6
+ const crypto = require('crypto');
6
7
  const runHook = require('../hooks');
7
8
  const event = require('../event');
8
9
  const collection = require('./run-multiple/collection');
9
10
  const clearString = require('../utils').clearString;
11
+ const replaceValueDeep = require('../utils').replaceValueDeep;
10
12
 
11
13
  const runner = path.join(__dirname, '/../../bin/codecept');
12
14
  let config;
@@ -61,48 +63,52 @@ module.exports = function (selectedRuns, options) {
61
63
  fail('No runs provided. Use --all option to run all configured runs');
62
64
  }
63
65
 
64
- const done = () => event.emit(event.multiple.before, null);
65
- runHook(config.bootstrapAll, done, 'bootstrapAll');
66
+ const done = () => {
67
+ event.emit(event.multiple.before, null);
68
+ if (options.config) { // update paths to config path
69
+ if (config.tests) {
70
+ config.tests = path.resolve(testRoot, config.tests);
71
+ }
72
+ if (config.gherkin && config.gherkin.features) {
73
+ config.gherkin.features = path.resolve(testRoot, config.gherkin.features);
74
+ }
75
+ }
66
76
 
67
- if (options.config) { // update paths to config path
68
- config.tests = path.resolve(testRoot, config.tests);
69
- if (config.gherkin && config.gherkin.features) {
70
- config.gherkin.features = path.resolve(testRoot, config.gherkin.features);
77
+ if (options.features) {
78
+ config.tests = '';
71
79
  }
72
- }
73
80
 
74
- if (options.features) {
75
- config.tests = '';
76
- }
81
+ if (options.tests && config.gherkin) {
82
+ config.gherkin.features = '';
83
+ }
77
84
 
78
- if (options.tests && config.gherkin) {
79
- config.gherkin.features = '';
80
- }
85
+ const childProcessesPromise = new Promise((resolve, reject) => {
86
+ processesDone = resolve;
87
+ });
81
88
 
82
- const childProcessesPromise = new Promise((resolve, reject) => {
83
- processesDone = resolve;
84
- });
89
+ const runsToExecute = [];
90
+ collection.createRuns(selectedRuns, config).forEach((run) => {
91
+ const runName = run.getOriginalName() || run.getName();
92
+ const runConfig = run.getConfig();
93
+ runsToExecute.push(executeRun(runName, runConfig));
94
+ });
85
95
 
86
- const runsToExecute = [];
87
- collection.createRuns(selectedRuns, config).forEach((run) => {
88
- const runName = run.getOriginalName() || run.getName();
89
- const runConfig = run.getConfig();
90
- runsToExecute.push(executeRun(runName, runConfig));
91
- });
96
+ if (!runsToExecute.length) {
97
+ fail('Nothing scheduled for execution');
98
+ }
92
99
 
93
- if (!runsToExecute.length) {
94
- fail('Nothing scheduled for execution');
95
- }
100
+ // Execute all forks
101
+ totalSubprocessCount = runsToExecute.length;
102
+ runsToExecute.forEach(runToExecute => runToExecute.call(this));
96
103
 
97
- // Execute all forks
98
- totalSubprocessCount = runsToExecute.length;
99
- runsToExecute.forEach(runToExecute => runToExecute.call(this));
104
+ return childProcessesPromise.then(() => {
105
+ // fire hook
106
+ const done = () => event.emit(event.multiple.after, null);
107
+ runHook(config.teardownAll, done, 'teardownAll');
108
+ });
109
+ };
100
110
 
101
- return childProcessesPromise.then(() => {
102
- // fire hook
103
- const done = () => event.emit(event.multiple.after, null);
104
- runHook(config.teardownAll, done, 'teardownAll');
105
- });
111
+ runHook(config.bootstrapAll, done, 'bootstrapAll');
106
112
  };
107
113
 
108
114
  function executeRun(runName, runConfig) {
@@ -114,23 +120,25 @@ function executeRun(runName, runConfig) {
114
120
  const browserName = browserConfig.browser;
115
121
 
116
122
  for (const key in browserConfig) {
117
- overriddenConfig.helpers = replaceValue(overriddenConfig.helpers, key, browserConfig[key]);
123
+ overriddenConfig.helpers = replaceValueDeep(overriddenConfig.helpers, key, browserConfig[key]);
118
124
  }
119
125
 
120
126
  let outputDir = `${runName}_`;
121
127
  if (browserConfig.outputName) {
122
128
  outputDir += typeof browserConfig.outputName === 'function' ? browserConfig.outputName() : browserConfig.outputName;
123
129
  } else {
124
- outputDir += JSON.stringify(browserConfig).replace(/[^\d\w]+/g, '_');
130
+ const hash = crypto.createHash('md5');
131
+ hash.update(JSON.stringify(browserConfig));
132
+ outputDir += hash.digest('hex');
125
133
  }
126
134
  outputDir += `_${runId}`;
127
135
 
128
136
  outputDir = clearString(outputDir);
129
137
 
130
138
  // tweaking default output directories and for mochawesome
131
- overriddenConfig = replaceValue(overriddenConfig, 'output', path.join(config.output, outputDir));
132
- overriddenConfig = replaceValue(overriddenConfig, 'reportDir', path.join(config.output, outputDir));
133
- overriddenConfig = replaceValue(overriddenConfig, 'mochaFile', path.join(config.output, outputDir, `${browserName}_report.xml`));
139
+ overriddenConfig = replaceValueDeep(overriddenConfig, 'output', path.join(config.output, outputDir));
140
+ overriddenConfig = replaceValueDeep(overriddenConfig, 'reportDir', path.join(config.output, outputDir));
141
+ overriddenConfig = replaceValueDeep(overriddenConfig, 'mochaFile', path.join(config.output, outputDir, `${browserName}_report.xml`));
134
142
 
135
143
  // override tests configuration
136
144
  if (overriddenConfig.tests) {
@@ -178,24 +186,3 @@ function executeRun(runName, runConfig) {
178
186
  return onProcessEnd(1);
179
187
  });
180
188
  }
181
-
182
-
183
- /**
184
- * search key in object recursive and replace value in it
185
- */
186
- function replaceValue(obj, key, value) {
187
- if (!obj) return;
188
- if (obj instanceof Array) {
189
- for (const i in obj) {
190
- replaceValue(obj[i], key, value);
191
- }
192
- }
193
- if (obj[key]) obj[key] = value;
194
- if (typeof obj === 'object' && obj !== null) {
195
- const children = Object.keys(obj);
196
- for (let childIndex = 0; childIndex < children.length; childIndex++) {
197
- replaceValue(obj[children[childIndex]], key, value);
198
- }
199
- }
200
- return obj;
201
- }
@@ -11,7 +11,7 @@ module.exports.getConfig = function (configFile) {
11
11
  try {
12
12
  return require('../config').load(configFile);
13
13
  } catch (err) {
14
- fail(err.message);
14
+ fail(err.stack);
15
15
  }
16
16
  };
17
17
 
package/lib/container.js CHANGED
@@ -167,7 +167,7 @@ function createSupportObjects(config) {
167
167
  objects[name] = {}; // placeholders
168
168
  }
169
169
 
170
- if (!objects.I) {
170
+ if (!config.I) {
171
171
  objects.I = require('./actor')();
172
172
 
173
173
  if (container.translation.I !== 'I') {
@@ -177,7 +177,7 @@ function createSupportObjects(config) {
177
177
 
178
178
  container.support = objects;
179
179
 
180
- for (const name in config) {
180
+ function lazyLoad(name) {
181
181
  let newObj = getSupportObject(config, name);
182
182
  try {
183
183
  if (typeof newObj === 'function') {
@@ -188,8 +188,9 @@ function createSupportObjects(config) {
188
188
  } catch (err) {
189
189
  throw new Error(`Initialization failed for ${name}: ${newObj}\n${err.message}`);
190
190
  }
191
- Object.assign(objects[name], newObj);
191
+ return newObj;
192
192
  }
193
+
193
194
  const asyncWrapper = function (f) {
194
195
  return function () {
195
196
  return f.apply(this, arguments).catch((e) => {
@@ -209,7 +210,32 @@ function createSupportObjects(config) {
209
210
  });
210
211
  });
211
212
 
212
- return objects;
213
+ return new Proxy({}, {
214
+ has(target, key) {
215
+ return key in config;
216
+ },
217
+ ownKeys() {
218
+ return Reflect.ownKeys(config);
219
+ },
220
+ get(target, key) {
221
+ // configured but not in support object, yet: load the module
222
+ if (key in objects && !(key in target)) {
223
+ // load default I
224
+ if (key in objects && !(key in config)) {
225
+ return target[key] = objects[key];
226
+ }
227
+
228
+ // load new object
229
+ const object = lazyLoad(key);
230
+ // check that object is a real object and not an array
231
+ if (Object.prototype.toString.call(object) === '[object Object]') {
232
+ return target[key] = Object.assign(objects[key], object);
233
+ }
234
+ target[key] = object;
235
+ }
236
+ return target[key];
237
+ },
238
+ });
213
239
  }
214
240
 
215
241
  function createPlugins(config, options = {}) {