Skip to content

Commit

Permalink
use Cucumber's JS API for tests (#14)
Browse files Browse the repository at this point in the history
* update dependencies

* make `run` an async function

* update cucumber again

* get everything passing

* update node versions for testing

* remove increased timeout

* revert module interop flag
  • Loading branch information
davidjgoss authored Jun 27, 2022
1 parent 21e4fc1 commit 646f2d5
Show file tree
Hide file tree
Showing 20 changed files with 977 additions and 779 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:

strategy:
matrix:
node: [10.x, 12.x, 14.x, 15.x]
node: [14.x, 16.x, 18.x]

steps:
- uses: actions/checkout@v2
Expand Down
1,253 changes: 710 additions & 543 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,23 @@
"@cucumber/messages": "*"
},
"devDependencies": {
"@cucumber/cucumber": "^7.3.0",
"@cucumber/messages": "^16.0.1",
"@cucumber/cucumber": "^8.3.1",
"@cucumber/messages": "^19.1.0",
"@types/glob": "^7.2.0",
"@types/mocha": "^8.2.0",
"@types/node": "^14.14.20",
"@typescript-eslint/eslint-plugin": "^4.12.0",
"@typescript-eslint/parser": "^4.12.0",
"colors": "^1.4.0",
"eslint": "^7.17.0",
"eslint-config-prettier": "^7.1.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-prettier": "^3.3.1",
"glob": "^7.2.3",
"mocha": "^8.2.1",
"prettier": "^2.2.1",
"should": "^13.2.3",
"stream-to-string": "^1.2.0",
"typescript": "^4.1.3"
}
}
5 changes: 3 additions & 2 deletions test/background.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import 'should'
import { run } from './exec'

describe('Background', () => {
it('does not log backgrounds', () => {
run('background.feature').should.startWith(
it('does not log backgrounds', async () => {
const result = await run('background.feature')
result.should.startWith(
'Feature: Background # test/features/background.feature:1\n' +
'\n' +
' Scenario: Background scenario # test/features/background.feature:6\n' +
Expand Down
5 changes: 3 additions & 2 deletions test/data-table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import 'should'
import { run } from './exec'

describe('Data Table', () => {
it('logs data tables', () => {
run('data-table.feature').should.containEql(
it('logs data tables', async () => {
const result = await run('data-table.feature')
result.should.containEql(
' When data table:\n' +
' │ foo │ bar │\n' +
' │ lorem │ ipsum │\n'
Expand Down
10 changes: 6 additions & 4 deletions test/description.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import 'should'
import { run } from './exec'

describe('Description', () => {
it('logs feature descriptions', () => {
run('description.feature').should.startWith(
it('logs feature descriptions', async () => {
const result = await run('description.feature')
result.should.startWith(
'Feature: Description # test/features/description.feature:1\n' +
'\n' +
' **I like**\n' +
Expand All @@ -14,8 +15,9 @@ describe('Description', () => {
)
})

it('does not log scenario descriptions', () => {
run('description.feature').should.containEql(
it('does not log scenario descriptions', async () => {
const result = await run('description.feature')
result.should.containEql(
' Scenario: Description scenario # test/features/description.feature:7\n' +
' When noop\n' +
' Then noop\n' +
Expand Down
10 changes: 6 additions & 4 deletions test/doc-string.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import 'should'
import { run } from './exec'

describe('Doc String', () => {
it('logs doc strings', () => {
run('doc-string.feature').should.containEql(
it('logs doc strings', async () => {
const result = await run('doc-string.feature')
result.should.containEql(
' When doc string:\n' +
' """\n' +
' foo\n' +
Expand All @@ -14,8 +15,9 @@ describe('Doc String', () => {
)
})

it('preserves doc string indentation', () => {
run('doc-string.feature').should.containEql(
it('preserves doc string indentation', async () => {
const result = await run('doc-string.feature')
result.should.containEql(
' When doc string:\n' +
' """\n' +
' foo\n' +
Expand Down
99 changes: 52 additions & 47 deletions test/exec.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,64 @@
import { execFileSync } from 'child_process'
import {
IConfiguration,
loadConfiguration,
runCucumber,
} from '@cucumber/cucumber/api'
import * as glob from 'glob'
import { join } from 'path'
import { PassThrough } from 'stream'
import * as streamToString from 'stream-to-string'
import { promisify } from 'util'

import { ThemeStyles } from '../src/theme'

const cmd = 'node_modules/.bin/cucumber-js'

type RunOptionalOptions = {
'--name'?: string
'--tags'?: string[]
}
type RunOptions = {
colorsEnabled?: boolean
theme?: Partial<ThemeStyles>
type FormatOptions = {
colorsEnabled: boolean
theme: Partial<ThemeStyles>
}
type FinalRunOptions = RunOptionalOptions & Required<RunOptions>

export const run = (
export const run = async (
fileName: string,
options: RunOptions & RunOptionalOptions = {},
cucumberOptions: Partial<IConfiguration> = {},
formatOptions: Partial<FormatOptions> = {},
throws = false
): string => {
const { colorsEnabled, theme }: FinalRunOptions = {
colorsEnabled: false,
theme: {},
...options,
}
const args = [
'--publish-quiet',
'--require',
join(__dirname, 'features'),
'--format',
join(__dirname, '..', 'src'),
'--format-options',
JSON.stringify({ colorsEnabled, theme }),
]
if (options['--name']) args.push('--name', options['--name'])
if (options['--tags']) args.push('--tags', options['--tags'].join(','))

return exec(throws, ...args, join('test', 'features', fileName)).replace(
/\d+m\d+\.\d+s/g,
'0m00.000s'
)
}

const exec = (throws: boolean, ...args: string[]): string => {
if (process.env.LOG_CUCUMBER_RUN) console.log(`${cmd} ${args.join(' ')}`)
): Promise<string> => {
// clear require cache for support code
const matches = await promisify(glob)('features/support/*', {
absolute: true,
cwd: __dirname,
})
matches.forEach((match) => delete require.cache[match])

let stdout: string
const configuration: Partial<IConfiguration> = {
...cucumberOptions,
format: [join(__dirname, '..', 'src')],
formatOptions: {
colorsEnabled: false,
theme: {},
...formatOptions,
},
paths: [join('test', 'features', fileName)],
publishQuiet: true,
require: [join(__dirname, 'features')],
}
const { runConfiguration } = await loadConfiguration({
provided: configuration,
})
const stdout = new PassThrough()
const stderr = new PassThrough()
try {
stdout = execFileSync(cmd, args, { stdio: 'pipe' }).toString()
} catch (error) {
stdout = error.stdout.toString()
if (throws) throw error
await runCucumber(runConfiguration, {
cwd: join(__dirname, '..', '..'),
stdout,
stderr,
})
} catch (ex) {
if (throws) {
throw ex
}
}

if (process.env.LOG_CUCUMBER_RUN) console.log(stdout)
return stdout
stdout.end()
stderr.end()
const result = await streamToString(stdout)
return result.replace(/\d+m\d+\.\d+s/g, '0m00.000s')
}
5 changes: 3 additions & 2 deletions test/feature.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import 'should'
import { run } from './exec'

describe('Feature', () => {
it('logs feature names and inserts new lines between scenarios and features', () => {
run('*.feature', { '--name': 'Feature \\d' }).should.startWith(
it('logs feature names and inserts new lines between scenarios and features', async () => {
const result = await run('*.feature', { name: ['Feature \\d'] })
result.should.startWith(
'Feature: The Feature # test/features/feature.feature:1\n' +
'\n' +
' Scenario: Feature 1 # test/features/feature.feature:7\n' +
Expand Down
14 changes: 5 additions & 9 deletions test/hook.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,15 @@ import 'should'
import { run } from './exec'

describe('Hook', () => {
it('does not log hooks', () => {
run('hook.feature').should.startWith(
'[[[BeforeAll]]]\n' +
'Feature: Hook # test/features/hook.feature:1\n' +
it('does not log hooks', async () => {
const result = await run('hook.feature')
result.should.startWith(
'Feature: Hook # test/features/hook.feature:1\n' +
'\n' +
' @before @after\n' +
' Scenario: Hook # test/features/hook.feature:4\n' +
'[[[Before]]]\n' +
' When noop\n' +
' Then noop\n' +
'[[[After]]]\n' +
'\n' +
'[[[AfterAll]]]\n'
' Then noop\n'
)
})
})
14 changes: 8 additions & 6 deletions test/i18n.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import 'should'
import { run } from './exec'

describe('Internationalization', () => {
it('logs French', () => {
run('fr.feature', {
'--name': 'Nom du Scénario',
}).should.startWith(
it('logs French', async () => {
const result = await run('fr.feature', {
name: ['Nom du Scénario'],
})
result.should.startWith(
'Fonctionnalité: Nom de la Fonctionnalité # test/features/fr.feature:2\n' +
'\n' +
' Scénario: Nom du Scénario # test/features/fr.feature:4\n' +
Expand All @@ -15,8 +16,9 @@ describe('Internationalization', () => {
)
})

it('logs Russian', () => {
run('ru.feature', { '--name': 'Сценарий name' }).should.startWith(
it('logs Russian', async () => {
const result = await run('ru.feature', { name: ['Сценарий name'] })
result.should.startWith(
'Функция: Функция Name # test/features/ru.feature:2\n' +
'\n' +
' Сценарий: Сценарий name # test/features/ru.feature:4\n' +
Expand Down
15 changes: 9 additions & 6 deletions test/rule.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'should'
import { run } from './exec'

describe('Rule', () => {
it('logs rules', () => {
it('logs rules', async () => {
const expectedOutput =
'Feature: Rule # test/features/rule.feature:1\n\
\n\
Expand All @@ -19,10 +19,11 @@ describe('Rule', () => {
\n\
Scenario: Rule 2 scenario # test/features/rule.feature:13\n\
Given noop\n'
run('rule.feature').should.startWith(expectedOutput)
const result = await run('rule.feature')
result.should.startWith(expectedOutput)
})

it('logs background steps in rules', () => {
it('logs background steps in rules', async () => {
const expectedOutput =
'Feature: Rule background # test/features/rule-background.feature:1\n\
\n\
Expand All @@ -31,11 +32,13 @@ describe('Rule', () => {
Scenario: Rule 1 scenario # test/features/rule-background.feature:8\n\
Given noop\n\
Given noop\n'
run('rule-background.feature').should.startWith(expectedOutput)
const result = await run('rule-background.feature')
result.should.startWith(expectedOutput)
})

it('offsets the scenario indentation', () => {
run('rule*.feature').should.startWith(
it('offsets the scenario indentation', async () => {
const result = await run('rule*.feature')
result.should.startWith(
'Feature: Rule background # test/features/rule-background.feature:1\n\
\n\
Rule: the rule\n\
Expand Down
9 changes: 5 additions & 4 deletions test/scenario-outline.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import 'should'
import { run } from './exec'

describe('Scenario Outline', () => {
it('logs scenario outlines', () => {
run('scenario-outline.feature', {
'--name': 'Scenario outline',
}).should.containEql(
it('logs scenario outlines', async () => {
const result = await run('scenario-outline.feature', {
name: ['Scenario outline'],
})
result.should.containEql(
// TODO: use the example location when running a scenario outline
'Feature: Scenario Outline # test/features/scenario-outline.feature:1\n' +
'\n' +
Expand Down
10 changes: 6 additions & 4 deletions test/scenario.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ import 'should'
import { run } from './exec'

describe('Scenario', () => {
it('logs scenario names', () => {
run('scenario.feature', { '--name': 'Scenario name' }).should.containEql(
it('logs scenario names', async () => {
const result = await run('scenario.feature', { name: ['Scenario name'] })
result.should.containEql(
' Scenario: Scenario name # test/features/scenario.feature:3\n'
)
})

it('logs new lines between scenarios', () => {
run('scenario.feature', { '--name': 'Scenario \\d' }).should.containEql(
it('logs new lines between scenarios', async () => {
const result = await run('scenario.feature', { name: ['Scenario \\d'] })
result.should.containEql(
'Feature: Scenario # test/features/scenario.feature:1\n' +
'\n' +
' Scenario: Scenario 1 # test/features/scenario.feature:7\n' +
Expand Down
Loading

0 comments on commit 646f2d5

Please sign in to comment.