Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ testpullfilecache*
package-lock.json
yarn.lock
/.vs
typings/types.d.ts
typings/types.d.ts
typings/promiseBasedTypes.d.ts
12 changes: 12 additions & 0 deletions docs/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,15 @@ declare namespace CodeceptJS {
}
}
```

## Full promise-based methods <Badge text="Since 3.3.6" type="warning"/>

All CodeceptJS methods return a promise; however, some of its are not typed as accordingly.
This feature, which is enabled by [configuration](https://codecept.io/configuration/), refers to alternative typescript definitions transforming all methods to asynchronous actions.

How to enable it?
- Add required configuration
```ts
fullPromiseBased: true;
```
- Refresh internal TypeScript definitions by running following command: `npx codeceptjs def`
3 changes: 2 additions & 1 deletion lib/command/definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ module.exports = function (genPath, options) {
helperPaths[name] = require;
helperNames.push(name);
} else {
helperNames.push(name);
const fullBasedPromised = codecept.config.fullPromiseBased;
helperNames.push(fullBasedPromised === true ? `${name}Ts` : name);
}

if (!actingHelpers.includes(name)) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"lint-fix": "eslint bin/ examples/ lib/ test/ translations/ runok.js --fix",
"docs": "./runok.js docs",
"test:unit": "mocha test/unit --recursive",
"test:runner": "mocha test/runner --recursive",
"test:runner": "mocha test/runner --recursive --timeout 5000",
"test": "npm run test:unit && npm run test:runner",
"test:appium-quick": "mocha test/helper/Appium_test.js --grep 'quick'",
"test:appium-other": "mocha test/helper/Appium_test.js --grep 'second'",
Expand Down
4 changes: 4 additions & 0 deletions runok.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ module.exports = {

async defTypings() {
console.log('Generate TypeScript definition');
// Generate definitions for promised-based helper methods
await npx('jsdoc -c typings/jsdocPromiseBased.conf.js');
fs.renameSync('typings/types.d.ts', 'typings/promiseBasedTypes.d.ts');
// Generate all other regular definitions
await npx('jsdoc -c typings/jsdoc.conf.js');
},

Expand Down
13 changes: 13 additions & 0 deletions test/data/sandbox/configs/definitions/codecept.promise.based.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
exports.config = {
tests: './*_test.js',
timeout: 10000,
output: './output',
helpers: {
FileSystem: {},
},
include: {},
bootstrap: false,
mocha: {},
name: 'sandbox',
fullPromiseBased: true,
};
6 changes: 3 additions & 3 deletions test/runner/bdd_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ describe('BDD Gherkin', () => {
});

it('should show all available steps', (done) => {
exec(`${runner} gherkin:steps --config ${codecept_dir}/codecept.bdd.json`, (err, stdout, stderr) => { //eslint-disable-line
exec(`${runner} gherkin:steps --config ${codecept_dir}/codecept.bdd.js`, (err, stdout, stderr) => { //eslint-disable-line
stdout.should.include('Gherkin');
stdout.should.include('/I have product with \\$(\\d+) price/');
stdout.should.include('step_definitions/my_steps.js:3:1');
Expand All @@ -206,7 +206,7 @@ describe('BDD Gherkin', () => {
});

it('should generate snippets for missing steps', (done) => {
exec(`${runner} gherkin:snippets --dry-run --config ${codecept_dir}/codecept.dummy.bdd.json`, (err, stdout, stderr) => { //eslint-disable-line
exec(`${runner} gherkin:snippets --dry-run --config ${codecept_dir}/codecept.dummy.bdd.js`, (err, stdout, stderr) => { //eslint-disable-line
stdout.should.include(`Given('I open a browser on a site', () => {
// From "support/dummy.feature" {"line":4,"column":5}
throw new Error('Not implemented yet');
Expand Down Expand Up @@ -277,7 +277,7 @@ When(/^I define a step with a \\( paren and a "(.*?)" string$/, () => {
});

it('should not generate duplicated steps', (done) => {
exec(`${runner} gherkin:snippets --dry-run --config ${codecept_dir}/codecept.duplicate.bdd.json`, (err, stdout, stderr) => { //eslint-disable-line
exec(`${runner} gherkin:snippets --dry-run --config ${codecept_dir}/codecept.duplicate.bdd.js`, (err, stdout, stderr) => { //eslint-disable-line
assert.equal(stdout.match(/I open a browser on a site/g).length, 1);
assert(!err);
done();
Expand Down
2 changes: 1 addition & 1 deletion test/runner/before_failure_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const exec = require('child_process').exec;

const runner = path.join(__dirname, '/../../bin/codecept.js');
const codecept_dir = path.join(__dirname, '/../data/sandbox');
const codecept_run = `${runner} run --config ${codecept_dir}/codecept.beforetest.failure.json `;
const codecept_run = `${runner} run --config ${codecept_dir}/codecept.beforetest.failure.js `;

describe('Failure in before', function () {
this.timeout(5000);
Expand Down
40 changes: 32 additions & 8 deletions test/runner/definitions_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('Definitions', function () {

describe('Static files', () => {
it('should have internal object that is available as variable codeceptjs', (done) => {
exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.json`, () => {
exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.js`, () => {
const types = typesFrom(`${codecept_dir}/steps.d.ts`);
types.should.be.valid;

Expand Down Expand Up @@ -79,7 +79,7 @@ describe('Definitions', function () {
});

it('def should create definition file with correct page def', (done) => {
exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.json`, (err, stdout) => {
exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.js`, (err, stdout) => {
stdout.should.include('Definitions were generated in steps.d.ts');
const types = typesFrom(`${codecept_dir}/steps.d.ts`);
types.should.be.valid;
Expand All @@ -94,7 +94,7 @@ describe('Definitions', function () {
});

it('def should create definition file given a config file', (done) => {
exec(`${runner} def --config ${codecept_dir}/../../codecept.ddt.json`, (err, stdout) => {
exec(`${runner} def --config ${codecept_dir}/../../codecept.ddt.js`, (err, stdout) => {
stdout.should.include('Definitions were generated in steps.d.ts');
const types = typesFrom(`${codecept_dir}/../../steps.d.ts`);
types.should.be.valid;
Expand All @@ -104,7 +104,7 @@ describe('Definitions', function () {
});

it('def should create definition file with support object', (done) => {
exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.json`, () => {
exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.js`, () => {
const types = typesFrom(`${codecept_dir}/steps.d.ts`);
types.should.be.valid;

Expand All @@ -128,7 +128,7 @@ describe('Definitions', function () {
});

it('def should create definition file with inject which contains support objects', (done) => {
exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.json`, () => {
exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.js`, () => {
const types = typesFrom(`${codecept_dir}/steps.d.ts`);
types.should.be.valid;

Expand All @@ -145,7 +145,7 @@ describe('Definitions', function () {
});

it('def should create definition file with inject which contains I object', (done) => {
exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.json`, (err) => {
exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.js`, (err) => {
assert(!err);
const types = typesFrom(`${codecept_dir}/steps.d.ts`);
types.should.be.valid;
Expand All @@ -165,7 +165,7 @@ describe('Definitions', function () {
});

it('def should create definition file with inject which contains I object from helpers', (done) => {
exec(`${runner} def --config ${codecept_dir}//codecept.inject.powi.json`, () => {
exec(`${runner} def --config ${codecept_dir}/codecept.inject.powi.js`, () => {
const types = typesFrom(`${codecept_dir}/steps.d.ts`);
types.should.be.valid;

Expand All @@ -179,7 +179,7 @@ describe('Definitions', function () {
});

it('def should create definition file with callback params', (done) => {
exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.json`, () => {
exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.js`, () => {
const types = typesFrom(`${codecept_dir}/steps.d.ts`);
types.should.be.valid;

Expand All @@ -193,6 +193,30 @@ describe('Definitions', function () {
done();
});
});

it('def should create definition file with promise-based feature', (done) => {
exec(`${runner} def --config ${codecept_dir}/codecept.promise.based.js`, (err, stdout) => {
stdout.should.include('Definitions were generated in steps.d.ts');
const types = typesFrom(`${codecept_dir}/steps.d.ts`);
types.should.be.valid;

const definitionFile = types.getSourceFileOrThrow(`${codecept_dir}/steps.d.ts`);
const extend = getExtends(definitionFile.getNamespaceOrThrow('CodeceptJS').getInterfaceOrThrow('I'));
extend.should.containSubset([{
methods: [{
name: 'amInPath',
returnType: 'Promise<any>',
parameters: [{ name: 'openPath', type: 'string' }],
}, {
name: 'seeFile',
returnType: 'Promise<any>',
parameters: [{ name: 'name', type: 'string' }],
}],
}]);
assert(!err);
done();
});
});
});

/** @type {Chai.ChaiPlugin */
Expand Down
6 changes: 3 additions & 3 deletions test/runner/init_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ describe('Init Command', function () {
});

it('init - Where should logs, screenshots, and reports to be stored? (./output)', async () => {
const result = await run([runner, 'init'], ['Y', ENTER, ENTER, DOWN, DOWN, DOWN, ENTER]);
const result = await run([runner, 'init'], ['Y', ENTER, ENTER, DOWN, DOWN, DOWN, ENTER, ENTER]);
result.should.include('? What helpers do you want to use? REST');
result.should.include('Where should logs, screenshots, and reports to be stored? (./output)');
});

it('init - Do you want localization for tests? (See https://codecept.io/translation/)', async () => {
const result = await run([runner, 'init'], ['Y', ENTER, ENTER, DOWN, DOWN, DOWN, ENTER, ENTER]);
const result = await run([runner, 'init'], ['Y', ENTER, ENTER, DOWN, DOWN, DOWN, ENTER, ENTER, ENTER]);
result.should.include('? Do you want localization for tests? (See https://codecept.io/translation/)');
result.should.include('❯ English (no localization)');
for (const item of ['de-DE', 'it-IT', 'fr-FR', 'ja-JP', 'pl-PL', 'pt-BR']) {
Expand All @@ -51,7 +51,7 @@ describe('Init Command', function () {
});

it('init - [REST] Endpoint of API you are going to test (http://localhost:3000/api)', async () => {
const result = await run([runner, 'init'], ['Y', ENTER, ENTER, DOWN, DOWN, DOWN, ENTER, ENTER, ENTER]);
const result = await run([runner, 'init'], ['Y', ENTER, ENTER, DOWN, DOWN, DOWN, ENTER, ENTER, ENTER, ENTER]);
result.should.include('Do you want localization for tests? (See https://codecept.io/translation/) Eng');
result.should.include('Configure helpers...');
result.should.include('? [REST] Endpoint of API you are going to test (http://localhost:3000/api)');
Expand Down
6 changes: 3 additions & 3 deletions test/runner/run_multiple_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const exec = require('child_process').exec;

const runner = path.join(__dirname, '/../../bin/codecept.js');
const codecept_dir = path.join(__dirname, '/../data/sandbox');
const codecept_run = `${runner} run-multiple --config ${codecept_dir}/codecept.multiple.json `;
const codecept_run = `${runner} run-multiple --config ${codecept_dir}/codecept.multiple.js `;

describe('CodeceptJS Multiple Runner', function () {
this.timeout(40000);
Expand Down Expand Up @@ -175,7 +175,7 @@ describe('CodeceptJS Multiple Runner', function () {

it('should exit with non-zero code for failures during init process', (done) => {
process.chdir(codecept_dir);
exec(`${runner} run-multiple --config codecept.multiple.initFailure.json default --all`, (err, stdout) => {
exec(`${runner} run-multiple --config codecept.multiple.initFailure.js default --all`, (err, stdout) => {
expect(err).not.toBeFalsy();
expect(err.code).toBe(1);
expect(stdout).toContain('Failed on FailureHelper');
Expand Down Expand Up @@ -235,7 +235,7 @@ describe('CodeceptJS Multiple Runner', function () {

it('should be executed with several module when described', (done) => {
process.chdir(codecept_dir);
exec(`${runner} ${_codecept_run}/codecept.require.multiple.several.json default`, (err, stdout) => {
exec(`${runner} ${_codecept_run}/codecept.require.multiple.several.js default`, (err, stdout) => {
stdout.should.include(moduleOutput);
stdout.should.include(moduleOutput2);
(stdout.match(new RegExp(moduleOutput, 'g')) || []).should.have.lengthOf(2);
Expand Down
2 changes: 1 addition & 1 deletion test/runner/timeout_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const debug_this_test = false;

const config_run_config = (config, grep, verbose = false) => `${codecept_run} ${verbose || debug_this_test ? '--verbose' : ''} --config ${codecept_dir}/configs/timeouts/${config} ${grep ? `--grep "${grep}"` : ''}`;

describe.only('CodeceptJS Timeouts', function () {
describe('CodeceptJS Timeouts', function () {
this.timeout(10000);

it('should stop test when timeout exceeded', (done) => {
Expand Down
8 changes: 8 additions & 0 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Project: https://github.com/codeception/codeceptjs/
/// <reference path="./types.d.ts" />
/// <reference path="./promiseBasedTypes.d.ts" />
/// <reference types="webdriverio" />
/// <reference path="./Mocha.d.ts" />
/// <reference types="joi" />
Expand Down Expand Up @@ -260,6 +261,13 @@ declare namespace CodeceptJS {
steps: Array<string>
};

/**
* Enable full promise-based helper methods for [TypeScript](https://codecept.io/typescript/) project.
* If true, all helper methods are typed as asynchronous;
* Otherwise, it remains as it works in versions prior to 3.3.6
*/
fullPromiseBased?: boolean;

[key: string]: any;
};

Expand Down
35 changes: 35 additions & 0 deletions typings/jsdoc.promiseBased.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Helps tsd-jsdoc to exports all helpers methods as Promise
// - Before parsing JS file, change class name and remove configuration already exported
// - For each method set by default 'Promise<any>' if there is no returns tag or if the returns tag doesn't handle a promise

const isHelper = (path) => path.includes('docs/build');
const isDocumentedMethod = (doclet) => doclet.undocumented !== true
&& doclet.kind === 'function'
&& doclet.scope === 'instance';
const shouldOverrideReturns = (doclet) => !doclet.returns
|| !doclet.returns[0].type
|| !doclet.returns[0].type.names[0].includes('Promise');

module.exports = {
handlers: {
beforeParse(e) {
if (isHelper(e.filename)) {
e.source = e.source
// add 'Ts' suffix to generate promise-based helpers definition
.replace(/class (.*) extends/, 'class $1Ts extends')
// rename parent class to fix the inheritance
.replace(/(@augments \w+)/, '$1Ts')
// do not export twice the configuration of the helpers
.replace(/\/\*\*(.+?(?=config))config = \{\};/s, '');
}
},
newDoclet: ({ doclet }) => {
if (isHelper(doclet.meta.path)
&& isDocumentedMethod(doclet)
&& shouldOverrideReturns(doclet)) {
doclet.returns = [];
doclet.addTag('returns', '{Promise<any>}');
}
},
},
};
13 changes: 13 additions & 0 deletions typings/jsdocPromiseBased.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
source: {
include: [
'./docs/build',
],
},
opts: {
template: 'node_modules/tsd-jsdoc/dist',
recurse: true,
destination: './typings/',
},
plugins: ['jsdoc.promiseBased.js', 'jsdoc.namespace.js', 'jsdoc-typeof-plugin'],
};
Loading