Repository: webdriverio/webdriverio (2025-07-28T19:35:37Z)

Issue:
[🐛 Bug]: Skipping tests in cucumber with @skip is not working
### Have you read the Contributing Guidelines on issues?

- [x] I have read the [Contributing Guidelines on issues](https://github.com/webdriverio/webdriverio/blob/main/CONTRIBUTING.md#reporting-new-issues).

### WebdriverIO Version

9.18.4

### Node.js Version

20.11.1

### Mode

Standalone Mode

### Which capabilities are you using?

```typescript
{
    browserName: 'chrome'
}
```

### What happened?

In a project with WebdriverIO v9 and Cucumber and a scenario with the tag `@skip(browserName=["chrome","safari"])`, the execution fails, because cucumber returns an error if one of the skipped browser is set on the capabilities.

### What is your expected behavior?

The execution should be successful, skipping the scenarios with the skip tag that matched with the current capabilities

### How to reproduce the bug.

I created a reproducible example here: https://github.com/Nyaran/webdriverio-cucumber-skip

Just download it, install dependencies and run
```shell
git clone https://github.com/Nyaran/webdriverio-cucumber-skip
webdriverio-cucumber-skip
npm i
npm run wdio
```

### Relevant log output

```typescript
[0-0] 2025-07-28T18:45:38.414Z ERROR @wdio/local-runner: Failed launching test session: Error: Tag expression "(not @skip\(browserName=\["chrome","safari"\]\))" could not be parsed because of syntax error: Illegal escape before "[".
[0-0]     at tokenize (/tmp/webdriverio-cucumber-skip/node_modules/@cucumber/tag-expressions/src/index.ts:104:15)
[0-0]     at parse (/tmp/webdriverio-cucumber-skip/node_modules/@cucumber/tag-expressions/src/index.ts:23:18)
[0-0]     at PickleTagFilter (/tmp/webdriverio-cucumber-skip/node_modules/@cucumber/cucumber/src/pickle_filter.ts:136:37)
[0-0]     at PickleFilter (/tmp/webdriverio-cucumber-skip/node_modules/@cucumber/cucumber/src/pickle_filter.ts:36:22)
[0-0]     at <anonymous> (/tmp/webdriverio-cucumber-skip/node_modules/@cucumber/cucumber/src/filter/filter_plugin.ts:15:28)
[0-0]     at PluginManager.transform (/tmp/webdriverio-cucumber-skip/node_modules/@cucumber/cucumber/src/plugin/plugin_manager.ts:87:30)
[0-0]     at loadSources (/tmp/webdriverio-cucumber-skip/node_modules/@cucumber/cucumber/src/api/load_sources.ts:50:47)
[0-0]     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
[0-0]     at CucumberAdapter.init (/tmp/webdriverio-cucumber-skip/node_modules/@wdio/cucumber-framework/build/index.js:219:22)
[0-0]     at Object.adapterFactory.init (/tmp/webdriverio-cucumber-skip/node_modules/@wdio/cucumber-framework/build/index.js:508:20)
[0-0] SYNCHRONOUS TERMINATION NOTICE: When explicitly exiting the process via process.exit or via a parent process, asynchronous tasks in your exitHooks will not run. Either remove these tasks, use gracefulExit() instead of process.exit(), or ensure your parent process sends a SIGINT to the process running this code.
[0-0] FAILED
```

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct

### Is there an existing issue for this?

- [x] I have searched the existing issues


Discussion:

Patch:
diff --git a/packages/wdio-cucumber-framework/src/utils.ts b/packages/wdio-cucumber-framework/src/utils.ts
index 6364d617da1..6dc58587ebc 100644
--- a/packages/wdio-cucumber-framework/src/utils.ts
+++ b/packages/wdio-cucumber-framework/src/utils.ts
@@ -222,7 +222,7 @@ export function generateSkipTagsFromCapabilities(capabilities: Capabilities.Reso
                 .find((filter: WebdriverIO.Capabilities) => Object.keys(filter)
                     .every((key: keyof WebdriverIO.Capabilities) => match((capabilities as Record<string, string>)[key], filter[key] as RegExp)))
             if (isSkip) {
-                generatedTags.push(`(not ${tag.replace(/[()[\]{}^$*+?.|\\]/g, '\\$&')})`)
+                generatedTags.push(`(not ${tag.replace(/[(){}^$*+?.|\\]/g, '\\$&')})`)
             }
         }
     })


Test patch:
diff --git a/packages/wdio-cucumber-framework/tests/fixtures/test_skip.feature b/packages/wdio-cucumber-framework/tests/fixtures/test_skip.feature
index 1f65865599f..bacc3c1d649 100644
--- a/packages/wdio-cucumber-framework/tests/fixtures/test_skip.feature
+++ b/packages/wdio-cucumber-framework/tests/fixtures/test_skip.feature
@@ -4,7 +4,8 @@ Feature: Test Feature
     Given a step passes
     When a step passes
 
-  @skip(browserName="chrome") 
+  @skip(browserName="chrome")
+  @skip(browserName=["firefox","safari"])
   Scenario: Scenario #1
     Given a step passes
     When a step passes
diff --git a/tests/cucumber/test.feature b/tests/cucumber/test.feature
index a8c5d702267..b6bcdb76702 100644
--- a/tests/cucumber/test.feature
+++ b/tests/cucumber/test.feature
@@ -18,6 +18,7 @@ Feature: Example feature
         Then  I should fail once but pass on the second run
 
     @skip(browserName="chrome")
+    @skip(browserName=["firefox","safari"])
     Scenario: Skipped... should never be executed
         Then  this test should fail
 
@@ -60,4 +61,4 @@ Feature: Example feature
 
     Scenario: Supports snapshot testing
         Then it can take a file snapshot
-        And  it can take an inline snapshot
\ No newline at end of file
+        And  it can take an inline snapshot
diff --git a/tests/smoke.runner.js b/tests/smoke.runner.js
index 46fccbb73fe..c061292ac73 100644
--- a/tests/smoke.runner.js
+++ b/tests/smoke.runner.js
@@ -482,6 +482,33 @@ const cucumberFileOption = async () => {
     )
 }
 
+/**
+ * Cucumber @skip() tag
+ */
+const cucumberSkipTag = async () => {
+    for (const browserName of ['chrome', 'firefox']) {
+        const { skippedSpecs } = await launch(
+            'cucumberTestrunner',
+            path.resolve(__dirname, 'helpers', 'cucumber-hooks.conf.js'),
+            {
+                capabilities: [{ browserName }],
+                specs: [
+                    path.resolve(__dirname, 'cucumber', 'test.feature'),
+                    path.resolve(__dirname, 'cucumber', 'test-skipped.feature')
+                ],
+                cucumberOpts: {
+                    tags: '(not @SKIPPED_TAG)',
+                    ignoreUndefinedDefinitions: true,
+                    retry: 1,
+                    retryTagFilter: '@retry',
+                    scenarioLevelReporter: true
+                }
+            }
+        )
+        assert.strictEqual(skippedSpecs, 1)
+    }
+}
+
 /**
  * wdio test run with custom service
  */
@@ -1136,6 +1163,7 @@ const jasmineAfterHookArgsValidation = async () => {
         cucumberPendingTest,
         cucumberReporter,
         cucumberFileOption,
+        cucumberSkipTag,
         standaloneTest,
         mochaAsyncTestrunner,
         customService,