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
23 changes: 23 additions & 0 deletions docs/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,29 @@ Like in Mocha you can use `x` and `only` to skip tests or to run a single test.
- `Scenario.only` - executes only the current test
- `xFeature` - skips current suite <Badge text="Since 2.6.6" type="warning"/>
- `Feature.skip` - skips the current suite <Badge text="Since 2.6.6" type="warning"/>
- `Feature.only` - executes only the current suite <Badge text="Since 3.7.5" type="warning"/>

When using `Feature.only`, only scenarios within that feature will be executed:

```js
Feature.only('My Important Feature')

Scenario('test something', ({ I }) => {
I.amOnPage('https://github.com')
I.see('GitHub')
})

Scenario('test something else', ({ I }) => {
I.amOnPage('https://github.com')
I.see('GitHub')
})

Feature('Another Feature') // This will be skipped

Scenario('will not run', ({ I }) => {
// This scenario will be skipped
})
```

## Todo Test

Expand Down
13 changes: 13 additions & 0 deletions lib/mocha/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,19 @@ module.exports = function (suite) {
return new FeatureConfig(suite)
}

/**
* Exclusive test suite - runs only this feature.
* @global
* @kind constant
* @type {CodeceptJS.IFeature}
*/
context.Feature.only = function (title, opts) {
const reString = `^${escapeRe(`${title}:`)}`
mocha.grep(new RegExp(reString))
process.env.FEATURE_ONLY = true
return context.Feature(title, opts)
}

/**
* Pending test suite.
* @global
Expand Down
20 changes: 0 additions & 20 deletions test/data/sandbox/configs/definitions/steps.d.ts

This file was deleted.

7 changes: 7 additions & 0 deletions test/data/sandbox/configs/only/codecept.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
exports.config = {
tests: './*_test.js',
output: './output',
bootstrap: null,
mocha: {},
name: 'only-test',
}
16 changes: 16 additions & 0 deletions test/data/sandbox/configs/only/edge_case_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Edge case test with special characters and complex titles
Feature.only('Feature with special chars: @test [brackets] (parens) & symbols')

Scenario('Scenario with special chars: @test [brackets] & symbols', () => {
console.log('Special chars scenario executed')
})

Scenario('Normal scenario', () => {
console.log('Normal scenario executed')
})

Feature('Regular Feature That Should Not Run')

Scenario('Should not run scenario', () => {
console.log('This should never execute')
})
9 changes: 9 additions & 0 deletions test/data/sandbox/configs/only/empty_feature_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Feature.only('Empty Feature')

// No scenarios in this feature

Feature('Regular Feature')

Scenario('Should not run', () => {
console.log('This should not run')
})
29 changes: 29 additions & 0 deletions test/data/sandbox/configs/only/only_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Feature.only('@OnlyFeature')

Scenario('@OnlyScenario1', () => {
console.log('Only Scenario 1 was executed')
})

Scenario('@OnlyScenario2', () => {
console.log('Only Scenario 2 was executed')
})

Scenario('@OnlyScenario3', () => {
console.log('Only Scenario 3 was executed')
})

Feature('@RegularFeature')

Scenario('@RegularScenario1', () => {
console.log('Regular Scenario 1 should NOT execute')
})

Scenario('@RegularScenario2', () => {
console.log('Regular Scenario 2 should NOT execute')
})

Feature('@AnotherRegularFeature')

Scenario('@AnotherRegularScenario', () => {
console.log('Another Regular Scenario should NOT execute')
})
43 changes: 43 additions & 0 deletions test/runner/only_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const path = require('path')
const exec = require('child_process').exec
const assert = require('assert')

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

describe('Feature.only', () => {
it('should run only scenarios in Feature.only and skip other features', done => {
exec(`${codecept_run} only_test.js`, (err, stdout, stderr) => {
stdout.should.include('Only Scenario 1 was executed')
stdout.should.include('Only Scenario 2 was executed')
stdout.should.include('Only Scenario 3 was executed')
stdout.should.not.include('Regular Scenario 1 should NOT execute')
stdout.should.not.include('Regular Scenario 2 should NOT execute')
stdout.should.not.include('Another Regular Scenario should NOT execute')

// Should show 3 passing tests
stdout.should.include('3 passed')

assert(!err)
done()
})
})

it('should work when there are multiple features with Feature.only selecting one', done => {
exec(`${codecept_run} only_test.js`, (err, stdout, stderr) => {
// Should only run the @OnlyFeature scenarios
stdout.should.include('@OnlyFeature --')
stdout.should.include('✔ @OnlyScenario1')
stdout.should.include('✔ @OnlyScenario2')
stdout.should.include('✔ @OnlyScenario3')

// Should not include other features
stdout.should.not.include('@RegularFeature')
stdout.should.not.include('@AnotherRegularFeature')

assert(!err)
done()
})
})
})
67 changes: 67 additions & 0 deletions test/unit/mocha/ui_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ describe('ui', () => {
constants.forEach(c => {
it(`context should contain ${c}`, () => expect(context[c]).is.ok)
})

it('context should contain Feature.only', () => {
expect(context.Feature.only).is.ok
expect(context.Feature.only).to.be.a('function')
})
})

describe('Feature', () => {
Expand Down Expand Up @@ -129,6 +134,68 @@ describe('ui', () => {
expect(suiteConfig.suite.opts).to.deep.eq({}, 'Features should have no skip info')
})

it('Feature can be run exclusively with only', () => {
// Create a new mocha instance to test grep behavior
const mocha = new Mocha()
let grepPattern = null

// Mock mocha.grep to capture the pattern
const originalGrep = mocha.grep
mocha.grep = function (pattern) {
grepPattern = pattern
return this
}

// Reset environment variable
delete process.env.FEATURE_ONLY

// Re-emit pre-require with our mocked mocha instance
suite.emit('pre-require', context, {}, mocha)

suiteConfig = context.Feature.only('exclusive feature', { key: 'value' })

expect(suiteConfig.suite.title).eq('exclusive feature')
expect(suiteConfig.suite.opts).to.deep.eq({ key: 'value' }, 'Feature.only should pass options correctly')
expect(suiteConfig.suite.pending).eq(false, 'Feature.only must not be pending')
expect(grepPattern).to.be.instanceOf(RegExp)
expect(grepPattern.source).eq('^exclusive feature:')
expect(process.env.FEATURE_ONLY).eq('true', 'FEATURE_ONLY environment variable should be set')

// Restore original grep
mocha.grep = originalGrep
})

it('Feature.only should work without options', () => {
// Create a new mocha instance to test grep behavior
const mocha = new Mocha()
let grepPattern = null

// Mock mocha.grep to capture the pattern
const originalGrep = mocha.grep
mocha.grep = function (pattern) {
grepPattern = pattern
return this
}

// Reset environment variable
delete process.env.FEATURE_ONLY

// Re-emit pre-require with our mocked mocha instance
suite.emit('pre-require', context, {}, mocha)

suiteConfig = context.Feature.only('exclusive feature without options')

expect(suiteConfig.suite.title).eq('exclusive feature without options')
expect(suiteConfig.suite.opts).to.deep.eq({}, 'Feature.only without options should have empty opts')
expect(suiteConfig.suite.pending).eq(false, 'Feature.only must not be pending')
expect(grepPattern).to.be.instanceOf(RegExp)
expect(grepPattern.source).eq('^exclusive feature without options:')
expect(process.env.FEATURE_ONLY).eq('true', 'FEATURE_ONLY environment variable should be set')

// Restore original grep
mocha.grep = originalGrep
})

it('Feature should correctly pass options to suite context', () => {
suiteConfig = context.Feature('not skipped suite', { key: 'value' })
expect(suiteConfig.suite.opts).to.deep.eq({ key: 'value' }, 'Features should have passed options')
Expand Down
5 changes: 3 additions & 2 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ declare namespace CodeceptJS {
interface IHook {}
interface IScenario {}
interface IFeature {
(title: string): FeatureConfig
(title: string, opts?: { [key: string]: any }): FeatureConfig
}
interface CallbackOrder extends Array<any> {}
interface SupportObject {
Expand Down Expand Up @@ -486,6 +486,7 @@ declare namespace CodeceptJS {
todo: IScenario
}
interface Feature extends IFeature {
only: IFeature
skip: IFeature
}
interface IData {
Expand Down Expand Up @@ -545,7 +546,7 @@ declare const Given: typeof CodeceptJS.addStep
declare const When: typeof CodeceptJS.addStep
declare const Then: typeof CodeceptJS.addStep

declare const Feature: typeof CodeceptJS.Feature
declare const Feature: CodeceptJS.Feature
declare const Scenario: CodeceptJS.Scenario
declare const xScenario: CodeceptJS.IScenario
declare const xFeature: CodeceptJS.IFeature
Expand Down
Loading