Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit ce16e92

Browse files
Copilotkobenguyent
andauthored
[FR] - Support feature.only like Scenario.only (#5087)
* Initial plan * Changes before error encountered Co-authored-by: kobenguyent <7845001+kobenguyent@users.noreply.github.com> * Add TypeScript types for Feature.only method * Changes before error encountered Co-authored-by: kobenguyent <7845001+kobenguyent@users.noreply.github.com> * Fix TypeScript test expectations for hook return types Co-authored-by: kobenguyent <7845001+kobenguyent@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kobenguyent <7845001+kobenguyent@users.noreply.github.com>
1 parent f295302 commit ce16e92

File tree

11 files changed

+297
-81
lines changed

11 files changed

+297
-81
lines changed

‎docs/basics.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,29 @@ Like in Mocha you can use `x` and `only` to skip tests or to run a single test.
961961
- `Scenario.only` - executes only the current test
962962
- `xFeature` - skips current suite <Badge text="Since 2.6.6" type="warning"/>
963963
- `Feature.skip` - skips the current suite <Badge text="Since 2.6.6" type="warning"/>
964+
- `Feature.only` - executes only the current suite <Badge text="Since 3.7.5" type="warning"/>
965+
966+
When using `Feature.only`, only scenarios within that feature will be executed:
967+
968+
```js
969+
Feature.only('My Important Feature')
970+
971+
Scenario('test something', ({ I }) => {
972+
I.amOnPage('https://github.com')
973+
I.see('GitHub')
974+
})
975+
976+
Scenario('test something else', ({ I }) => {
977+
I.amOnPage('https://github.com')
978+
I.see('GitHub')
979+
})
980+
981+
Feature('Another Feature') // This will be skipped
982+
983+
Scenario('will not run', ({ I }) => {
984+
// This scenario will be skipped
985+
})
986+
```
964987
965988
## Todo Test
966989

‎lib/mocha/ui.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,19 @@ module.exports = function (suite) {
103103
return new FeatureConfig(suite)
104104
}
105105

106+
/**
107+
* Exclusive test suite - runs only this feature.
108+
* @global
109+
* @kind constant
110+
* @type {CodeceptJS.IFeature}
111+
*/
112+
context.Feature.only = function (title, opts) {
113+
const reString = `^${escapeRe(`${title}:`)}`
114+
mocha.grep(new RegExp(reString))
115+
process.env.FEATURE_ONLY = true
116+
return context.Feature(title, opts)
117+
}
118+
106119
/**
107120
* Pending test suite.
108121
* @global

‎test/data/sandbox/configs/definitions/steps.d.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
exports.config = {
2+
tests: './*_test.js',
3+
output: './output',
4+
bootstrap: null,
5+
mocha: {},
6+
name: 'only-test',
7+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Edge case test with special characters and complex titles
2+
Feature.only('Feature with special chars: @test [brackets] (parens) & symbols')
3+
4+
Scenario('Scenario with special chars: @test [brackets] & symbols', () => {
5+
console.log('Special chars scenario executed')
6+
})
7+
8+
Scenario('Normal scenario', () => {
9+
console.log('Normal scenario executed')
10+
})
11+
12+
Feature('Regular Feature That Should Not Run')
13+
14+
Scenario('Should not run scenario', () => {
15+
console.log('This should never execute')
16+
})
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Feature.only('Empty Feature')
2+
3+
// No scenarios in this feature
4+
5+
Feature('Regular Feature')
6+
7+
Scenario('Should not run', () => {
8+
console.log('This should not run')
9+
})
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Feature.only('@OnlyFeature')
2+
3+
Scenario('@OnlyScenario1', () => {
4+
console.log('Only Scenario 1 was executed')
5+
})
6+
7+
Scenario('@OnlyScenario2', () => {
8+
console.log('Only Scenario 2 was executed')
9+
})
10+
11+
Scenario('@OnlyScenario3', () => {
12+
console.log('Only Scenario 3 was executed')
13+
})
14+
15+
Feature('@RegularFeature')
16+
17+
Scenario('@RegularScenario1', () => {
18+
console.log('Regular Scenario 1 should NOT execute')
19+
})
20+
21+
Scenario('@RegularScenario2', () => {
22+
console.log('Regular Scenario 2 should NOT execute')
23+
})
24+
25+
Feature('@AnotherRegularFeature')
26+
27+
Scenario('@AnotherRegularScenario', () => {
28+
console.log('Another Regular Scenario should NOT execute')
29+
})

‎test/runner/only_test.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const path = require('path')
2+
const exec = require('child_process').exec
3+
const assert = require('assert')
4+
5+
const runner = path.join(__dirname, '/../../bin/codecept.js')
6+
const codecept_dir = path.join(__dirname, '/../data/sandbox/configs/only')
7+
const codecept_run = `${runner} run --config ${codecept_dir}/codecept.conf.js `
8+
9+
describe('Feature.only', () => {
10+
it('should run only scenarios in Feature.only and skip other features', done => {
11+
exec(`${codecept_run} only_test.js`, (err, stdout, stderr) => {
12+
stdout.should.include('Only Scenario 1 was executed')
13+
stdout.should.include('Only Scenario 2 was executed')
14+
stdout.should.include('Only Scenario 3 was executed')
15+
stdout.should.not.include('Regular Scenario 1 should NOT execute')
16+
stdout.should.not.include('Regular Scenario 2 should NOT execute')
17+
stdout.should.not.include('Another Regular Scenario should NOT execute')
18+
19+
// Should show 3 passing tests
20+
stdout.should.include('3 passed')
21+
22+
assert(!err)
23+
done()
24+
})
25+
})
26+
27+
it('should work when there are multiple features with Feature.only selecting one', done => {
28+
exec(`${codecept_run} only_test.js`, (err, stdout, stderr) => {
29+
// Should only run the @OnlyFeature scenarios
30+
stdout.should.include('@OnlyFeature --')
31+
stdout.should.include('✔ @OnlyScenario1')
32+
stdout.should.include('✔ @OnlyScenario2')
33+
stdout.should.include('✔ @OnlyScenario3')
34+
35+
// Should not include other features
36+
stdout.should.not.include('@RegularFeature')
37+
stdout.should.not.include('@AnotherRegularFeature')
38+
39+
assert(!err)
40+
done()
41+
})
42+
})
43+
})

‎test/unit/mocha/ui_test.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ describe('ui', () => {
2828
constants.forEach(c => {
2929
it(`context should contain ${c}`, () => expect(context[c]).is.ok)
3030
})
31+
32+
it('context should contain Feature.only', () => {
33+
expect(context.Feature.only).is.ok
34+
expect(context.Feature.only).to.be.a('function')
35+
})
3136
})
3237

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

137+
it('Feature can be run exclusively with only', () => {
138+
// Create a new mocha instance to test grep behavior
139+
const mocha = new Mocha()
140+
let grepPattern = null
141+
142+
// Mock mocha.grep to capture the pattern
143+
const originalGrep = mocha.grep
144+
mocha.grep = function (pattern) {
145+
grepPattern = pattern
146+
return this
147+
}
148+
149+
// Reset environment variable
150+
delete process.env.FEATURE_ONLY
151+
152+
// Re-emit pre-require with our mocked mocha instance
153+
suite.emit('pre-require', context, {}, mocha)
154+
155+
suiteConfig = context.Feature.only('exclusive feature', { key: 'value' })
156+
157+
expect(suiteConfig.suite.title).eq('exclusive feature')
158+
expect(suiteConfig.suite.opts).to.deep.eq({ key: 'value' }, 'Feature.only should pass options correctly')
159+
expect(suiteConfig.suite.pending).eq(false, 'Feature.only must not be pending')
160+
expect(grepPattern).to.be.instanceOf(RegExp)
161+
expect(grepPattern.source).eq('^exclusive feature:')
162+
expect(process.env.FEATURE_ONLY).eq('true', 'FEATURE_ONLY environment variable should be set')
163+
164+
// Restore original grep
165+
mocha.grep = originalGrep
166+
})
167+
168+
it('Feature.only should work without options', () => {
169+
// Create a new mocha instance to test grep behavior
170+
const mocha = new Mocha()
171+
let grepPattern = null
172+
173+
// Mock mocha.grep to capture the pattern
174+
const originalGrep = mocha.grep
175+
mocha.grep = function (pattern) {
176+
grepPattern = pattern
177+
return this
178+
}
179+
180+
// Reset environment variable
181+
delete process.env.FEATURE_ONLY
182+
183+
// Re-emit pre-require with our mocked mocha instance
184+
suite.emit('pre-require', context, {}, mocha)
185+
186+
suiteConfig = context.Feature.only('exclusive feature without options')
187+
188+
expect(suiteConfig.suite.title).eq('exclusive feature without options')
189+
expect(suiteConfig.suite.opts).to.deep.eq({}, 'Feature.only without options should have empty opts')
190+
expect(suiteConfig.suite.pending).eq(false, 'Feature.only must not be pending')
191+
expect(grepPattern).to.be.instanceOf(RegExp)
192+
expect(grepPattern.source).eq('^exclusive feature without options:')
193+
expect(process.env.FEATURE_ONLY).eq('true', 'FEATURE_ONLY environment variable should be set')
194+
195+
// Restore original grep
196+
mocha.grep = originalGrep
197+
})
198+
132199
it('Feature should correctly pass options to suite context', () => {
133200
suiteConfig = context.Feature('not skipped suite', { key: 'value' })
134201
expect(suiteConfig.suite.opts).to.deep.eq({ key: 'value' }, 'Features should have passed options')

‎typings/index.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ declare namespace CodeceptJS {
440440
interface IHook {}
441441
interface IScenario {}
442442
interface IFeature {
443-
(title: string): FeatureConfig
443+
(title: string,opts?: {[key: string]: any}): FeatureConfig
444444
}
445445
interface CallbackOrder extends Array<any> {}
446446
interface SupportObject {
@@ -486,6 +486,7 @@ declare namespace CodeceptJS {
486486
todo: IScenario
487487
}
488488
interface Feature extends IFeature {
489+
only: IFeature
489490
skip: IFeature
490491
}
491492
interface IData {
@@ -545,7 +546,7 @@ declare const Given: typeof CodeceptJS.addStep
545546
declare const When: typeof CodeceptJS.addStep
546547
declare const Then: typeof CodeceptJS.addStep
547548

548-
declare const Feature: typeofCodeceptJS.Feature
549+
declare const Feature: CodeceptJS.Feature
549550
declare const Scenario: CodeceptJS.Scenario
550551
declare const xScenario: CodeceptJS.IScenario
551552
declare const xFeature: CodeceptJS.IFeature

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /