diff --git a/lib/command/definitions.js b/lib/command/definitions.js index 380d19ed7..72db5e583 100644 --- a/lib/command/definitions.js +++ b/lib/command/definitions.js @@ -9,7 +9,7 @@ const fs = require('fs'); const path = require('path'); const template = ` -type ICodeceptCallback = (i: CodeceptJS.{{I}}{{callbackParams}}) => void; +type ICodeceptCallback = (i?: CodeceptJS.{{I}}, current?:any{{callbackParams}}) => void; declare class FeatureConfig { retry(times: number): FeatureConfig @@ -41,6 +41,134 @@ interface ILocator { type LocatorOrString = string | ILocator | Locator; +declare class Container { + create(config: Object, opts: Object): void + plugins(name?: string): Object + support(name?: string): Object + helpers(name?: string): Object + translation(): Object + mocha(): Object + append(newContainer: Object): void + clear(newHelpers: Object, newSupport: Object, newPlugins: Object): void +} + +declare class RecorderSession { + running: boolean + start(name: string): void + restore(name: string): void + catch(fn: CallableFunction): void +} + +declare class Recorder { + retries: Object[] + start(): void + isRunning(): boolean + startUnlessRunning(): void + errHandler(fn: CallableFunction): void + reset(): void + session: RecorderSession + add(taskName, fn?: CallableFunction, force?: boolean, retry?: boolean): Promise + retry(opts: Object): Promise + catch(customErrFn: CallableFunction): Promise + catchWithoutStop(customErrFn: CallableFunction ): Promise + throw(err: Error): Promise + saveFirstAsyncError(err: Error): void + getAsyncErr(): Promise + cleanAsyncErr(): void + stop():void + promise(): Promise + scheduled(): string[] + toString(): string + add(hookName: string, fn: CallableFunction, force?: boolean): void + catch(customErrFn: CallableFunction) +} + +declare class CodeceptJSEvent { + dispatcher: EventEmitter + test: { + started: string + before: string + after: string + passed: string + failed: string + finished: string + } + suite: { + before: string, + after: string, + } + hook: { + started: string, + passed: string, + } + step: { + before: string, + after: string, + started: string, + passed: string, + failed: string, + finished: string, + } + all: { + before: string, + after: string, + result: string, + } + multiple: { + before: string, + after: string, + } + emit(event: string, param: string) + cleanDispatcher(): void +} + +declare class Output { + colors: any + styles: { + error: any, + success: any, + scenario: any, + basic: any, + debug: any, + log: any, + } + + print(msg: string): void + stepShift: number + level(level: number): number + process(process: string): string + debug(msg: string): void + log(msg: string): void + error(msg: string): void + success(msg: string): void + plugin(name: string, msg: string): void + step(step: any): void + suite: { + started: Function + } + test: { + started(test: string): void + passed(test: string): void + failed(test: string): void + skipped(test: string): void + } + scenario: { + started(test: string): void + passed(test: string): void + failed(test: string): void + } + say(message: string, color?: string): void + result(passed: number, failed: number, skipped: number, duration: string): void +} + +declare class Config { + create(newConfig: Object): Object + load(configFile: string): Config + get(key: string, val: any): any + append(additionalConfig: Object): Object + reset(): Object +} + declare class Helper { /** Abstract method to provide required config options */ static _config(): any; @@ -129,7 +257,9 @@ declare function BeforeSuite(callback: ICodeceptCallback): void; declare function After(callback: ICodeceptCallback): void; declare function AfterSuite(callback: ICodeceptCallback): void; -declare function inject(): { [key: string]: any }; +declare function inject(): { +{{injectPageObjects}} +}; declare function locate(selector: LocatorOrString): Locator; declare function within(selector: LocatorOrString, callback: Function): Promise; declare function session(selector: LocatorOrString, callback: Function): Promise; @@ -140,6 +270,12 @@ declare function secret(secret: any): string; declare const codeceptjs: any; declare namespace CodeceptJS { + export const container: Container + export const recorder: Recorder + export const event: CodeceptJSEvent + export const output: Output + export const config: Config + export interface {{I}} { {{methods}} } @@ -151,6 +287,8 @@ declare module "codeceptjs" { } `; +const injectSupportTemplate = ' {{name}}: CodeceptJS.{{name}}'; + const pageObjectTemplate = ` export interface {{name}} { {{methods}} @@ -181,20 +319,26 @@ module.exports = function (genPath, options) { const supports = container.support(); // return all support objects const exportPageObjects = []; + const injectPageObjects = []; const callbackParams = []; // See #1795 and #1799, there's no obvious way to use for-in with Proxies Reflect.ownKeys(supports).forEach((name) => { - if (name === 'I') return; - callbackParams.push(`${String(name)}:CodeceptJS.${String(name)}`); + if (name === 'I') { + injectPageObjects.push(injectSupportTemplate.replace(/{{name}}/g, String(name))); + return; + } + callbackParams.push(`${String(name)}?:CodeceptJS.${String(name)}`); const pageObject = supports[name]; const pageMethods = addAllMethodsInObject(pageObject, {}, []); let pageObjectExport = pageObjectTemplate.replace('{{methods}}', pageMethods.join('')); pageObjectExport = pageObjectExport.replace('{{name}}', String(name)); + injectPageObjects.push(injectSupportTemplate.replace(/{{name}}/g, String(name))); exportPageObjects.push(pageObjectExport); }); let definitionsTemplate = template.replace('{{methods}}', methods.join('')); definitionsTemplate = definitionsTemplate.replace('{{exportPageObjects}}', exportPageObjects.join('\n')); + definitionsTemplate = definitionsTemplate.replace('{{injectPageObjects}}', injectPageObjects.join('\n')); definitionsTemplate = definitionsTemplate.replace('{{callbackParams}}', `, ${[...callbackParams, '...args: any'].join(', ')}`); if (translations) { definitionsTemplate = definitionsTemplate.replace(/\{\{I\}\}/g, translations.I); diff --git a/test/data/sandbox/codecept.inject.po.json b/test/data/sandbox/codecept.inject.po.json index a914e7f34..5dde02132 100644 --- a/test/data/sandbox/codecept.inject.po.json +++ b/test/data/sandbox/codecept.inject.po.json @@ -7,7 +7,8 @@ }, "include": { "I": "./support/custom_steps.js", - "MyPage": "./support/my_page.js" + "MyPage": "./support/my_page.js", + "SecondPage": "./support/second_page.js" }, "bootstrap": false, "mocha": {}, diff --git a/test/data/sandbox/support/second_page.js b/test/data/sandbox/support/second_page.js new file mode 100644 index 000000000..adfd44c9e --- /dev/null +++ b/test/data/sandbox/support/second_page.js @@ -0,0 +1,5 @@ +module.exports = { + secondPageMethod() { + console.log('secondPageMethod'); + }, +}; diff --git a/test/runner/list_test.js b/test/runner/list_test.js index 90785d379..825c26e0c 100644 --- a/test/runner/list_test.js +++ b/test/runner/list_test.js @@ -62,4 +62,63 @@ describe('list/def commands', () => { done(); }); }); + + it('def should create definition file with internal object', (done) => { + try { + fs.unlinkSync(`${codecept_dir}/steps.d.ts`); + } catch (e) { + // continue regardless of error + } + exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.json`, () => { + const content = fs.readFileSync(`${codecept_dir}/steps.d.ts`).toString(); + content.should.include(' export const recorder: Recorder'); + content.should.include(' export const event: CodeceptJSEvent'); + content.should.include(' export const output: Output'); + content.should.include(' export const config: Config'); + content.should.include(' export const container: Container'); + done(); + }); + }); + + it('def should create definition file with inject which contains support objects', (done) => { + try { + fs.unlinkSync(`${codecept_dir}/steps.d.ts`); + } catch (e) { + // continue regardless of error + } + exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.json`, () => { + const content = fs.readFileSync(`${codecept_dir}/steps.d.ts`).toString(); + content.should.include('declare function inject(): {'); + content.should.include(' MyPage: CodeceptJS.MyPage'); + content.should.include(' SecondPage: CodeceptJS.SecondPage'); + done(); + }); + }); + + it('def should create definition file with inject which contains I object', (done) => { + try { + fs.unlinkSync(`${codecept_dir}/steps.d.ts`); + } catch (e) { + // continue regardless of error + } + exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.json`, () => { + const content = fs.readFileSync(`${codecept_dir}/steps.d.ts`).toString(); + content.should.include('declare function inject(): {'); + content.should.include(' I: CodeceptJS.I'); + done(); + }); + }); + + it('def should create definition file with callback params', (done) => { + try { + fs.unlinkSync(`${codecept_dir}/steps.d.ts`); + } catch (e) { + // continue regardless of error + } + exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.json`, () => { + const content = fs.readFileSync(`${codecept_dir}/steps.d.ts`).toString(); + content.should.include('type ICodeceptCallback = (i?: CodeceptJS.I, current?:any, MyPage?:CodeceptJS.MyPage, SecondPage?:CodeceptJS.SecondPage, ...args: any) => void;'); + done(); + }); + }); });