Repository: TheBrainFamily/cypress-cucumber-preprocessor
Pull request #122 (2018-12-08 04:58:52)

feat: Smart Tagging


Patch:
diff --git a/README.md b/README.md
index 425d21c..85b9d20 100644
--- a/README.md
+++ b/README.md
@@ -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
@@ -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!)
@@ -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. 
 
@@ -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. 
diff --git a/cypress/integration/SmartTagging.feature b/cypress/integration/SmartTagging.feature
new file mode 100644
index 0000000..03333e6
--- /dev/null
+++ b/cypress/integration/SmartTagging.feature
@@ -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
diff --git a/cypress/support/step_definitions/smart_tagging.js b/cypress/support/step_definitions/smart_tagging.js
new file mode 100644
index 0000000..60b94ba
--- /dev/null
+++ b/cypress/support/step_definitions/smart_tagging.js
@@ -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);
+});
diff --git a/lib/index.js b/lib/index.js
index f7a80d5..2a183d2 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -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);
       });


Test patch:
diff --git a/lib/createTestsFromFeature.js b/lib/createTestsFromFeature.js
index a1308be..ef9a0d2 100644
--- a/lib/createTestsFromFeature.js
+++ b/lib/createTestsFromFeature.js
@@ -5,26 +5,40 @@ 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"
       );
@@ -32,11 +46,14 @@ const createTestsFromFeature = parsedFeature => {
         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);
         }
diff --git a/lib/resolveStepDefinition.test.js b/lib/resolveStepDefinition.test.js
index 4a97c42..226ff3d 100644
--- a/lib/resolveStepDefinition.test.js
+++ b/lib/resolveStepDefinition.test.js
@@ -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")
+  );
+});