diff --git a/features/order.feature b/features/order.feature index a2296332f..03d1d0170 100644 --- a/features/order.feature +++ b/features/order.feature @@ -1,23 +1,23 @@ Feature: Set the execution order - Background: + Background: Given a file named "features/a.feature" with: """ Feature: some feature @a Scenario: first scenario Given a step - + @b Scenario Outline: second scenario - Given a step - + @c Examples: | ID | | X | | Y | - + @d Examples: | ID | @@ -26,7 +26,7 @@ Feature: Set the execution order And a file named "features/step_definitions/cucumber_steps.js" with: """ const {Given} = require('@cucumber/cucumber') - + Given(/^a step$/, function() {}) """ @@ -47,3 +47,40 @@ Feature: Set the execution order | second scenario - X | | second scenario - Y | | first scenario | + + Rule: Scenarios can be run in the order of a rerun file + + Scenario: run in rerun order + Given a file named "@rerun.txt" with: + """ + features/a.feature:19 + features/a.feature:3 + features/a.feature:14 + """ + When I run cucumber-js with `--order rerun @rerun.txt` + Then it runs the scenarios: + | NAME | + | second scenario - Z | + | first scenario | + | second scenario - Y | + + Scenario: run in rerun order with one incorrect line number + Given a file named "@rerun.txt" with: + """ + features/a.feature:19 + features/a.feature:2 + features/a.feature:14 + """ + When I run cucumber-js with `--order rerun @rerun.txt` + Then it runs the scenarios: + | NAME | + | second scenario - Z | + | second scenario - Y | + + Scenario: run in rerun order without a rerun file + When I run cucumber-js with `--order rerun` + Then it fails + And the error output contains the text: + """ + Cannot use rerun order because features/a.feature:13 was not in the rerun order. Did you forget to specify @rerun.txt? + """ diff --git a/src/api/gherkin.ts b/src/api/gherkin.ts index 8fd19c1f2..9a30e4aa7 100644 --- a/src/api/gherkin.ts +++ b/src/api/gherkin.ts @@ -2,25 +2,12 @@ import { GherkinStreams, IGherkinStreamOptions, } from '@cucumber/gherkin-streams' -import { - Envelope, - GherkinDocument, - IdGenerator, - Location, - ParseError, - Pickle, -} from '@cucumber/messages' +import { Envelope, IdGenerator, ParseError } from '@cucumber/messages' import { Query as GherkinQuery } from '@cucumber/gherkin-utils' import PickleFilter from '../pickle_filter' -import { orderPickles } from '../cli/helpers' +import { orderPickles, PickleWithDocument } from '../cli/helpers' import { ISourcesCoordinates } from './types' -interface PickleWithDocument { - gherkinDocument: GherkinDocument - location: Location - pickle: Pickle -} - export async function getFilteredPicklesAndErrors({ newId, cwd, @@ -85,7 +72,12 @@ export async function getFilteredPicklesAndErrors({ pickle, } }) - orderPickles(filteredPickles, coordinates.order, logger) + orderPickles( + filteredPickles, + coordinates.order, + unexpandedFeaturePaths, + logger + ) return { filteredPickles, parseErrors, diff --git a/src/cli/helpers.ts b/src/cli/helpers.ts index 866321202..49a3302a7 100644 --- a/src/cli/helpers.ts +++ b/src/cli/helpers.ts @@ -7,7 +7,12 @@ import { OptionSplitter } from '../configuration' import { Readable } from 'stream' import os from 'os' import * as messages from '@cucumber/messages' -import { IdGenerator } from '@cucumber/messages' +import { + GherkinDocument, + IdGenerator, + Location, + Pickle, +} from '@cucumber/messages' import detectCiEnvironment from '@cucumber/ci-environment' import { ISupportCodeLibrary } from '../support_code_library_builder/types' import TestCaseHookDefinition from '../models/test_case_hook_definition' @@ -16,12 +21,19 @@ import { PickleOrder } from '../models/pickle_order' import { builtinParameterTypes } from '../support_code_library_builder' import { version } from '../version' +export interface PickleWithDocument { + gherkinDocument: GherkinDocument + location: Location + pickle: Pickle +} + interface IParseGherkinMessageStreamRequest { cwd?: string eventBroadcaster: EventEmitter eventDataCollector: EventDataCollector gherkinMessageStream: Readable order: string + unexpandedFeaturePaths?: string[] pickleFilter: PickleFilter } @@ -34,6 +46,7 @@ interface IParseGherkinMessageStreamRequest { * @param eventDataCollector * @param gherkinMessageStream * @param order + * @param unexpandedFeaturePaths * @param pickleFilter */ export async function parseGherkinMessageStream({ @@ -41,6 +54,7 @@ export async function parseGherkinMessageStream({ eventDataCollector, gherkinMessageStream, order, + unexpandedFeaturePaths, pickleFilter, }: IParseGherkinMessageStreamRequest): Promise { return await new Promise((resolve, reject) => { @@ -59,7 +73,7 @@ export async function parseGherkinMessageStream({ } }) gherkinMessageStream.on('end', () => { - orderPickles(result, order, console) + orderPickles(result, order, unexpandedFeaturePaths, console) resolve(result) }) gherkinMessageStream.on('error', reject) @@ -70,6 +84,7 @@ export async function parseGherkinMessageStream({ export function orderPickles( pickleIds: T[], order: PickleOrder, + unexpandedFeaturePaths: string[] | undefined, logger: Console ): void { const [type, seed] = OptionSplitter.split(order) @@ -85,6 +100,36 @@ export function orderPickles( shuffle(pickleIds, seed) } break + case 'rerun': + { + if (unexpandedFeaturePaths === undefined) { + throw new Error( + 'Cannot order by rerun because no unexpandedFeaturePaths were provided' + ) + } + + const picklesWithDocument = + pickleIds as unknown[] as PickleWithDocument[] + + picklesWithDocument.sort((a, b) => { + const pathA = `${a.pickle.uri}:${a.location.line}` + const pathB = `${b.pickle.uri}:${b.location.line}` + const indexA = unexpandedFeaturePaths.indexOf(pathA) + const indexB = unexpandedFeaturePaths.indexOf(pathB) + if (indexA === -1) { + throw new Error( + `Cannot use rerun order because ${pathA} was not in the rerun order. Did you forget to specify @rerun.txt?` + ) + } + if (indexB === -1) { + throw new Error( + `Cannot use rerun order because ${pathB} was not in the rerun order. Did you forget to specify @rerun.txt?` + ) + } + return indexA - indexB + }) + } + break default: throw new Error( 'Unrecgonized order type. Should be `defined` or `random`' diff --git a/src/configuration/argv_parser.ts b/src/configuration/argv_parser.ts index 193df328f..396414a54 100644 --- a/src/configuration/argv_parser.ts +++ b/src/configuration/argv_parser.ts @@ -112,7 +112,7 @@ const ArgvParser = { .option( '--order ', - 'run scenarios in the specified order. Type should be `defined` or `random`' + "run scenarios in the specified order. Type should be 'defined', 'random' or 'rerun'" ) .option( '-p, --profile ', diff --git a/src/models/pickle_order.ts b/src/models/pickle_order.ts index d3fc77e0e..35e89b00a 100644 --- a/src/models/pickle_order.ts +++ b/src/models/pickle_order.ts @@ -1 +1 @@ -export type PickleOrder = 'defined' | 'random' | string +export type PickleOrder = 'defined' | 'random' | 'rerun' | string