Skip to content

Commit

Permalink
feat: Smart Tagging (#122)
Browse files Browse the repository at this point in the history
* feat: step definitions related to the feature file (#121)

* docs: added information about smart tagging
  • Loading branch information
lgandecki authored Dec 8, 2018
1 parent e0d09ca commit 081f95b
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 21 deletions.
36 changes: 33 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
[![CircleCI](https://circleci.com/gh/TheBrainFamily/cypress-cucumber-preprocessor.svg?style=shield)](https://circleci.com/gh/TheBrainFamily/cypress-cucumber-preprocessor)
# Run cucumber/gherkin-syntaxed specs with cypress.io

Follow the Setup steps, or if you prefer to hack on a working example, take a look [https://github.com/TheBrainFamily/cypress-cucumber-example](https://github.com/TheBrainFamily/cypress-cucumber-example
Follow the Setup steps, or if you prefer to hack on a working example, take a look at [https://github.com/TheBrainFamily/cypress-cucumber-example](https://github.com/TheBrainFamily/cypress-cucumber-example
)

## Setup
Expand Down Expand Up @@ -34,12 +34,15 @@ Example: cypress/integration/Google.feature
Feature: The Facebook
I want to open a social network page
@focus
Scenario: Opening a social network page
Given I open Google page
Then I see "google" in the title
```

(the @focus tag is not necessary, but we want to you to notice it so you look it up below. *It will speed up your workflow significantly*!)

### Step definitions

#### Cypress Cucumber Preprocessor Style (recommended!)
Expand Down Expand Up @@ -93,7 +96,7 @@ The problem with the legacy structure is that everything is global. This is prob
- It makes it harder to create .feature files that read nicely - you have to make sure you are not stepping on toes of already existing step definitions. You should be able to write your tests without worrying about reusability, complex regexp matches, or anything like that. Just write a story. Explain what you want to see without getting into the details. Reuse in the .js files, not in something you should consider an always up-to-date, human-readable documentation.
- The startup times get much worse - because we have to analyze all the different step definitions so we can match the .feature files to the test.
- Hooks are problematic. If you put before() in a step definition file, you might think that it will run only for the .feature file related to that step definition. You try the feature you work on, everything seems fine and you push the code. Here comes a surprise - it will run for ALL .feature files in your whole project. Very unintuitive. And good luck debugging problems caused by that! This problem was not unique to this plugin, bo to the way cucumberjs operates.
Let's look how this differs with the proposed structure. Assuming you want to have a hook before ./Google.feature file, just create a ./Google/before.js and put the hook there. This should take care of long requested feature - (https://github.com/TheBrainFamily/cypress-cucumber-preprocessor/issues/25)[#25]
Let's look how this differs with the proposed structure. Assuming you want to have a hook before ./Google.feature file, just create a ./Google/before.js and put the hook there. This should take care of long requested feature - [https://github.com/TheBrainFamily/cypress-cucumber-preprocessor/issues/25](#25)

If you have a few tests the "oldschool" style is completely fine. But for a large enterprise-grade application, with hundreds or sometimes thousands of .feature files, the fact that everything is global becomes a maintainability nightmare.

Expand Down Expand Up @@ -224,6 +227,33 @@ In order to initialize tests using tags you will have to run cypress and pass TA
Example:
```cypress run -e TAGS='not @foo and (@bar or @zap)'```
### Smart tagging
Start your tests without setting any tags. And then put a @focus on the scenario (or scenarios) you want to focus on while development or bug fixing.
For example:
```gherkin
Feature: Smart Tagging

As a cucumber cypress plugin which handles Tags
I want to allow people to select tests to run if focused
So they can work more efficiently and have a shorter feedback loop

Scenario: This scenario should not run if @focus is on another scenario
Then this unfocused scenario should not run

@focus
Scenario: This scenario is focused and should run
Then this focused scenario should run

@this-tag-affects-nothing
Scenario: This scenario should also not run
Then this unfocused scenario should not run

@focus
Scenario: This scenario is also focused and also should run
Then this focused scenario should run
```
## Custom Parameter Type Resolves
Thanks to @Oltodo we can now use Custom Parameter Type Resolves.
Expand Down
20 changes: 20 additions & 0 deletions cypress/integration/SmartTagging.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Feature: Smart Tagging

As a cucumber cypress plugin which handles Tags
I want to allow people to select tests to run if focused
So they can work more efficiently and have a shorter feedback loop

Scenario: This scenario should not run if @focus is on another scenario
Then this unfocused scenario should not run

@focus
Scenario: This scenario is focused and should run
Then this focused scenario should run

@this-tag-affects-nothing
Scenario: This scenario should also not run
Then this unfocused scenario should not run

@focus
Scenario: This scenario is also focused and also should run
Then this focused scenario should run
11 changes: 11 additions & 0 deletions cypress/support/step_definitions/smart_tagging.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const assert = require("assert");
// eslint-disable-next-line import/no-extraneous-dependencies
const { Then } = require("cypress-cucumber-preprocessor/steps");

Then(`this focused scenario should run`, () => {
assert(true);
});

Then(`this unfocused scenario should not run`, () => {
assert(false);
});
51 changes: 34 additions & 17 deletions lib/createTestsFromFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,55 @@ const createTestsFromFeature = parsedFeature => {
const featureTags = parsedFeature.feature.tags;
const hasEnvTags = !!getEnvTags();
const hasFeatureTags = featureTags && featureTags.length > 0;
const sectionsWithTags = parsedFeature.feature.children.filter(
section => section.tags && section.tags.length
);

const sectionsWithTagsExist = sectionsWithTags.length > 0;

let featureShouldRun = true;
let everythingShouldRun = false;
let featureShouldRun = false;
let taggedScenarioShouldRun = false;
let anyFocused = false;
if (hasEnvTags) {
if (hasFeatureTags) {
featureShouldRun = shouldProceedCurrentStep(featureTags);
featureShouldRun = shouldProceedCurrentStep(featureTags);
taggedScenarioShouldRun = parsedFeature.feature.children.some(
section =>
section.tags &&
section.tags.length &&
shouldProceedCurrentStep(section.tags)
);
} else if (!hasFeatureTags && !sectionsWithTagsExist) {
everythingShouldRun = true;
} else if (sectionsWithTagsExist) {
anyFocused = sectionsWithTags.some(section =>
section.tags.find(t => t.name === "@focus")
);
if (anyFocused) {
taggedScenarioShouldRun = true;
} else {
featureShouldRun = false;
everythingShouldRun = true;
}
}

const taggedScenarioShouldRun = parsedFeature.feature.children.some(
section =>
section.tags &&
section.tags.length &&
shouldProceedCurrentStep(section.tags)
);

// eslint-disable-next-line prefer-arrow-callback
describe(parsedFeature.feature.name, function() {
if (featureShouldRun || taggedScenarioShouldRun) {
if (everythingShouldRun || featureShouldRun || taggedScenarioShouldRun) {
const backgroundSection = parsedFeature.feature.children.find(
section => section.type === "Background"
);
const otherSections = parsedFeature.feature.children.filter(
section => section.type !== "Background"
);
otherSections.forEach(section => {
const scenarioHasTags = section.tags.length > 0;
const shouldRun =
hasEnvTags && scenarioHasTags
? shouldProceedCurrentStep(section.tags.concat(featureTags)) // concat handles inheritance of tags from feature
: featureShouldRun;
let shouldRun;
if (anyFocused) {
shouldRun = section.tags.find(t => t.name === "@focus");
} else {
shouldRun =
everythingShouldRun ||
shouldProceedCurrentStep(section.tags.concat(featureTags)); // concat handles inheritance of tags from feature
}
if (shouldRun) {
createTestFromScenario(section, backgroundSection);
}
Expand Down
4 changes: 3 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ const preprocessor = (options = browserify.defaultOptions) => file => {
watcher.close();
}
watcher = chokidar
.watch(stepDefinitionPath(), { ignoreInitial: true })
.watch([`${stepDefinitionPath()}*.js`, `${stepDefinitionPath()}*.ts`], {
ignoreInitial: true
})
.on("all", () => {
touch(file.filePath);
});
Expand Down
11 changes: 11 additions & 0 deletions lib/resolveStepDefinition.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,14 @@ describe("Tags with env TAGS set", () => {
)
);
});

describe("Smart tagging", () => {
window.Cypress = {
env: () => ""
};
require("../cypress/support/step_definitions/smart_tagging");

createTestsFromFeature(
readAndParseFeatureFile("./cypress/integration/SmartTagging.feature")
);
});

0 comments on commit 081f95b

Please sign in to comment.