diff --git a/.gitignore b/.gitignore
index 09251501..a43ca066 100644
--- a/.gitignore
+++ b/.gitignore
@@ -77,4 +77,7 @@ jspm_packages/
# Temporary files and playground
tmp
-*.tmp
\ No newline at end of file
+*.tmp
+
+CLAUDE.md
+.planning/
diff --git a/examples/multiProject/README.md b/examples/multiProject/README.md
index 0539bf1c..8314df2f 100644
--- a/examples/multiProject/README.md
+++ b/examples/multiProject/README.md
@@ -184,12 +184,38 @@ Project codes (e.g. `PROJ1`, `PROJ2`) must match `testops_multi.projects[].code`
When you run the examples:
-1. **Test runs are created** in each configured project (e.g. DEVX and DEMO).
+1. **Test runs are created** in each configured project (e.g. PROJ1 and PROJ2).
2. **Test results are sent** to the appropriate projects based on the mapping you specify.
3. **Each project has its own test run** with the title and description from config.
-4. **Results appear** in both projects’ dashboards.
+4. **Results appear** in both projects' dashboards.
5. **Tests without any Qase ID** are sent to the `default_project` without linking to a test case.
+## Expected Behavior by Framework
+
+### CucumberJS
+* Uses Gherkin tag syntax: `@qaseid.PROJ1(1)` and `@qaseid.PROJ2(2)` in feature files
+* Each scenario can be reported to multiple projects using separate tags
+* Native Gherkin Given/When/Then steps are automatically captured
+* Attachments via `this.attach()` in step definitions
+
+### Newman
+* Uses comment-based markers: `// qase PROJ1: 1` and `// qase PROJ2: 2` in test scripts
+* Multiple comments before `pm.test()` map the same test to multiple projects
+* No programmatic steps or attachments API (Postman limitation)
+* Collection structure determines test organization
+
+### TestCafe
+* Uses builder pattern: `test.meta(qase.projects({ PROJ1: [1], PROJ2: [2] }).create())`
+* Chaining supported: `qase.projects({...}).title('...').fields({...}).create()`
+* Nested steps use callback parameter (s, s1, s2) for hierarchy
+* Attachments use `type` parameter (not `contentType`)
+
+### WDIO
+* **Mocha/Jasmine mode:** `it(qase.projects({ PROJ1: [1], PROJ2: [2] }, 'Test name'), () => {...})`
+* **Cucumber mode:** Uses Gherkin tags like CucumberJS
+* Supports both programmatic steps and Gherkin steps depending on framework
+* Reporter options: `disableWebdriverStepsReporting`, `useCucumber`
+
## Mapping Tests to Projects (Summary)
| Reporter | Helper / usage |
diff --git a/examples/single/cucumberjs/README.md b/examples/single/cucumberjs/README.md
new file mode 100644
index 00000000..8f832975
--- /dev/null
+++ b/examples/single/cucumberjs/README.md
@@ -0,0 +1,81 @@
+# CucumberJS Example
+
+This is a sample project demonstrating how to write and execute tests using the CucumberJS framework with integration to Qase Test Management.
+
+## Prerequisites
+
+Ensure that the following tools are installed on your machine:
+
+1. [Node.js](https://nodejs.org/) (version 18 or higher is recommended)
+2. [npm](https://www.npmjs.com/) or [yarn](https://yarnpkg.com/)
+
+## Setup Instructions
+
+1. Clone this repository by running the following commands:
+ ```bash
+ git clone https://github.com/qase-tms/qase-javascript.git
+ cd qase-javascript/examples/single/cucumberjs
+ ```
+
+2. Install the project dependencies:
+ ```bash
+ npm install
+ ```
+
+3. Create a `qase.config.json` file in the root of the project. Follow the instructions on [how to configure the file](https://github.com/qase-tms/qase-javascript/tree/main/qase-javascript-commons#configuration).
+
+## Example Files
+
+This example includes:
+
+* **features/** — Gherkin feature files with test scenarios
+ * `simple.feature` — Basic scenarios with Qase tags (@QaseID, @QaseTitle, @QaseFields)
+ * `table.feature` — Examples using data tables
+* **step_definitions/** — Step implementation files
+ * `simple_steps.js` — Step definitions using native Cucumber Given/When/Then
+ * `table_steps.js` — Step definitions for table-based scenarios
+* **qase.config.json** — Qase reporter configuration
+
+## Running Tests
+
+To run tests locally without reporting to Qase:
+
+```bash
+QASE_MODE=off npm test
+```
+
+To run tests and upload the results to Qase Test Management:
+
+```bash
+npm test
+```
+
+Or with explicit mode:
+
+```bash
+QASE_MODE=testops npx cucumber-js -f cucumberjs-qase-reporter
+```
+
+## Expected Behavior
+
+When tests execute with Qase reporting enabled:
+
+* **Gherkin scenarios** are reported as individual test cases in Qase
+* **Given/When/Then/And steps** from feature files are automatically reported as Qase test steps
+* **@QaseID tags** link scenarios to existing test cases in your Qase project
+* **@QaseTitle tags** override the default scenario name in Qase
+* **@QaseFields tags** add metadata (severity, priority, etc.) to test results
+* **Attachments** added via `this.attach()` in step definitions are included in Qase results
+
+## Framework-Specific Features
+
+CucumberJS with Qase has unique patterns:
+
+* **No programmatic qase.step() API** — Steps come from Gherkin syntax (Given/When/Then)
+* **Native Cucumber attachments** — Use `this.attach(content, mimeType)` in step definitions (not `qase.attach()`)
+* **Tag-based metadata** — Test configuration uses Gherkin tags instead of programmatic calls
+* **Feature-based organization** — Test suite hierarchy comes from Feature/Scenario structure
+
+## Additional Resources
+
+For more details on how to use this integration with Qase Test Management, visit the [Qase CucumberJS documentation](https://github.com/qase-tms/qase-javascript/tree/main/qase-cucumberjs).
diff --git a/examples/single/cypress/README.md b/examples/single/cypress/README.md
index 5dec013c..981cf9e5 100644
--- a/examples/single/cypress/README.md
+++ b/examples/single/cypress/README.md
@@ -15,7 +15,7 @@ Ensure that the following tools are installed on your machine:
1. Clone this repository by running the following commands:
```bash
git clone https://github.com/qase-tms/qase-javascript.git
- cd qase-js/examples/cypress
+ cd qase-javascript/examples/single/cypress
```
2. Install the project dependencies:
@@ -26,18 +26,131 @@ Ensure that the following tools are installed on your machine:
3. Create a `qase.config.json` file in the root of the project. Follow the instructions
on [how to configure the file](https://github.com/qase-tms/qase-javascript/tree/main/qase-javascript-commons#configuration).
-4. Run the Cypress tests locally:
+4. To run tests locally without Qase reporting (interactive mode):
```bash
- npx cypress open
+ QASE_MODE=off npx cypress open
```
- This will open the Cypress Test Runner, where you can manually execute tests.
-5. To run tests and upload the results to Qase Test Management, use the following command:
+5. To run tests locally without Qase reporting (headless mode):
```bash
- npx cypress run --env qaseApiToken=YOUR_QASE_API_TOKEN,qaseProjectCode=YOUR_PROJECT_CODE
+ QASE_MODE=off npx cypress run
```
- Replace `YOUR_QASE_API_TOKEN` and `YOUR_PROJECT_CODE` with your Qase API token and project code.
+6. To run tests and upload the results to Qase Test Management:
+ ```bash
+ QASE_MODE=testops npx cypress run
+ ```
+
+## Example Files
+
+This project contains several test files demonstrating different Qase features:
+
+| File | Feature | Description |
+|------|---------|-------------|
+| `simpleTests.cy.js` | Basic tests | Simple Cypress tests with and without Qase integration |
+| `methodTests.cy.js` | Qase methods | Demonstrates `qase.comment()` and `qase.attach()` methods |
+| `stepTests.cy.js` | Test steps | Defines execution steps with `qase.step()` (synchronous callbacks) |
+| `parametrizedTests.cy.js` | Parameters | Reports parameterized test data with `qase.parameters()` |
+
+## Expected Behavior
+
+### Running with QASE_MODE=off (Local Development)
+
+When running tests with `QASE_MODE=off`, tests execute normally without Qase reporting:
+
+- Tests run and pass/fail as usual
+- No data is sent to Qase TestOps
+- No Qase API token required
+- Output shows standard Cypress test results
+- Cypress screenshots and videos work normally
+
+This mode is useful for local development and debugging.
+
+### Running with QASE_MODE=testops (CI/CD and Reporting)
+
+When running tests with `QASE_MODE=testops`, test results are reported to Qase:
+
+- Tests execute and results are sent to Qase TestOps
+- A new test run is created in your Qase project
+- Test results include all metadata (steps, attachments, comments, etc.)
+- Console output includes Qase test run link
+- Requires valid `QASE_TESTOPS_API_TOKEN` and `QASE_TESTOPS_PROJECT` configuration
+- Cypress screenshots on failure can be attached automatically
+
+**Steps Example (`stepTests.cy.js`):**
+- Creates test result with multiple named steps using `qase.step()`
+- Each step shows execution status, duration, and any errors
+- **Important:** Cypress steps use synchronous callbacks (no async/await)
+- Nested steps can be created by calling `qase.step()` within another step
+- Steps are visible in Qase test run details
+
+**Attachments Example (`methodTests.cy.js`):**
+- Content attached via `qase.attach()` appears in test results
+- Supports attaching text, JSON, and other content types
+- Cypress screenshots and videos can be attached automatically on failure
+- Attachments are visible in the test run details
+
+**Parameters Example (`parametrizedTests.cy.js`):**
+- Parameterized tests report their parameter values to Qase
+- Parameters help identify which test variant produced which result
+- Useful for data-driven testing scenarios
+
+**Multi-Project Support:**
+- When configured for multi-project reporting, same test results are sent to multiple Qase projects
+- Each project can have different test case IDs for the same test
+
+## Configuration
+
+Example `qase.config.json`:
+
+```json
+{
+ "mode": "testops",
+ "debug": false,
+ "testops": {
+ "api": {
+ "token": "your_api_token_here"
+ },
+ "project": "YOUR_PROJECT_CODE",
+ "run": {
+ "title": "Cypress Automated Test Run",
+ "complete": true
+ }
+ }
+}
+```
+
+Or configure via `cypress.config.js`:
+
+```javascript
+const { defineConfig } = require('cypress');
+
+module.exports = defineConfig({
+ reporter: 'cypress-qase-reporter',
+ reporterOptions: {
+ mode: 'testops',
+ testops: {
+ api: {
+ token: process.env.QASE_TESTOPS_API_TOKEN,
+ },
+ project: 'YOUR_PROJECT_CODE',
+ run: {
+ complete: true,
+ },
+ },
+ },
+ e2e: {
+ setupNodeEvents(on, config) {
+ // implement node event listeners here
+ },
+ },
+});
+```
+
+## Important Notes
+
+- **Synchronous Steps:** Unlike Jest or Playwright, Cypress steps use synchronous callbacks. Do NOT use `async/await` with `qase.step()` in Cypress tests.
+- **Import Pattern:** Use `import { qase } from 'cypress-qase-reporter/mocha';` (note the `/mocha` suffix, as Cypress uses Mocha under the hood)
## Additional Resources
diff --git a/examples/single/jest/README.md b/examples/single/jest/README.md
index e5ecff79..dab633bd 100644
--- a/examples/single/jest/README.md
+++ b/examples/single/jest/README.md
@@ -15,7 +15,7 @@ Ensure that the following tools are installed on your machine:
1. Clone this repository by running the following commands:
```bash
git clone https://github.com/qase-tms/qase-javascript.git
- cd qase-js/examples/jest
+ cd qase-javascript/examples/single/jest
```
2. Install the project dependencies:
@@ -26,11 +26,117 @@ Ensure that the following tools are installed on your machine:
3. Create a `qase.config.json` file in the root of the project. Follow the instructions
on [how to configure the file](https://github.com/qase-tms/qase-javascript/tree/main/qase-javascript-commons#configuration).
-4. To run tests and upload the results to Qase Test Management, use the following command:
+4. To run tests locally without Qase reporting:
```bash
- npx jest --runInBand
+ QASE_MODE=off npm test
```
+5. To run tests and upload the results to Qase Test Management:
+ ```bash
+ QASE_MODE=testops npm test
+ ```
+
+## Example Files
+
+This project contains several test files demonstrating different Qase features:
+
+| File | Feature | Description |
+|------|---------|-------------|
+| `id.test.js` | Test case linking | Links test to Qase test case by ID using `qase(id, name)` wrapper |
+| `title.test.js` | Custom titles | Sets custom test result titles with `qase.title()` |
+| `fields.test.js` | Custom fields | Sets severity, priority, description, and other metadata with `qase.fields()` |
+| `suite.test.js` | Suite organization | Groups tests into suites and sub-suites with `qase.suite()` |
+| `steps.test.js` | Test steps | Defines execution steps with `await qase.step()` for detailed reporting |
+| `attach.test.js` | Attachments | Attaches files and content to test results with `qase.attach()` |
+| `comment.test.js` | Comments | Adds comments to test results with `qase.comment()` |
+| `ignore.test.js` | Ignoring tests | Excludes tests from Qase reporting with `qase.ignore()` |
+| `params.test.js` | Parameters | Reports parameterized test data with `qase.parameters()` |
+
+## Expected Behavior
+
+### Running with QASE_MODE=off (Local Development)
+
+When running tests with `QASE_MODE=off`, tests execute normally without Qase reporting:
+
+- Tests run and pass/fail as usual
+- No data is sent to Qase TestOps
+- No Qase API token required
+- Output shows standard Jest test results
+
+This mode is useful for local development and debugging.
+
+### Running with QASE_MODE=testops (CI/CD and Reporting)
+
+When running tests with `QASE_MODE=testops`, test results are reported to Qase:
+
+- Tests execute and results are sent to Qase TestOps
+- A new test run is created in your Qase project
+- Test results include all metadata (steps, attachments, fields, etc.)
+- Console output includes Qase test run link
+- Requires valid `QASE_TESTOPS_API_TOKEN` and `QASE_TESTOPS_PROJECT` configuration
+
+**Steps Example (`steps.test.js`):**
+- Creates test result with multiple named steps
+- Each step shows execution status, duration, and any errors
+- Nested steps appear hierarchically in Qase
+- Steps with expected results and data are captured
+
+**Attachments Example (`attach.test.js`):**
+- Files attached via `paths` option appear in test results
+- Content attached via `content` option is uploaded to Qase
+- Attachments are visible in the test run details
+- Supports text, JSON, images, and binary files
+
+**Multi-Project Support:**
+- When configured for multi-project reporting, same test results are sent to multiple Qase projects
+- Each project can have different test case IDs for the same test
+
+## Configuration
+
+Example `qase.config.json`:
+
+```json
+{
+ "mode": "testops",
+ "debug": false,
+ "testops": {
+ "api": {
+ "token": "your_api_token_here"
+ },
+ "project": "YOUR_PROJECT_CODE",
+ "run": {
+ "title": "Jest Automated Test Run",
+ "complete": true
+ }
+ }
+}
+```
+
+Or configure via `jest.config.js`:
+
+```javascript
+module.exports = {
+ reporters: [
+ 'default',
+ [
+ 'jest-qase-reporter',
+ {
+ mode: 'testops',
+ testops: {
+ api: {
+ token: process.env.QASE_TESTOPS_API_TOKEN,
+ },
+ project: 'YOUR_PROJECT_CODE',
+ run: {
+ complete: true,
+ },
+ },
+ },
+ ],
+ ],
+};
+```
+
## Additional Resources
For more details on how to use this integration with Qase Test Management, visit
diff --git a/examples/single/mocha/README.md b/examples/single/mocha/README.md
index ddbea635..7eab23d1 100644
--- a/examples/single/mocha/README.md
+++ b/examples/single/mocha/README.md
@@ -96,6 +96,46 @@ This runs Mocha directly with Qase integration.
- The project code is shown in the URL: `https://app.qase.io/project/YOUR_PROJECT_CODE`
- Or find it in project settings
+## Expected Behavior
+
+### Running with QASE_MODE=off (Local Development)
+
+When running tests with `QASE_MODE=off`, tests execute normally without Qase reporting:
+
+- Tests run and pass/fail as usual
+- No data is sent to Qase TestOps
+- No Qase API token required
+- Output shows standard Mocha test results
+
+This mode is useful for local development and debugging.
+
+### Running with QASE_MODE=testops (CI/CD and Reporting)
+
+When running tests with `QASE_MODE=testops`, test results are reported to Qase:
+
+- Tests execute and results are sent to Qase TestOps
+- A new test run is created in your Qase project
+- Test results include all metadata (steps, attachments, fields, etc.)
+- Console output includes Qase test run link
+- Requires valid Qase API token and project code in `qase.config.json`
+
+**Steps Example:**
+- Creates test result with multiple named steps using `this.step()`
+- Each step shows execution status, duration, and any errors
+- Nested steps appear hierarchically in Qase
+- Steps with callbacks can contain assertions and actions
+
+**Attachments Example:**
+- Files attached via `this.attach()` appear in test results
+- Content can be strings, buffers, or file paths
+- Attachments are visible in the test run details
+- Supports text, JSON, images, and binary files
+
+**Context Methods:**
+- Mocha provides Qase methods via test context (`this` keyword)
+- Must use `function()` syntax (not arrow functions) to access `this`
+- Available methods: `this.title()`, `this.suite()`, `this.fields()`, `this.comment()`, `this.parameters()`, `this.attach()`, `this.step()`
+
## What You'll See
### Test Execution Output
diff --git a/examples/single/newman/README.md b/examples/single/newman/README.md
new file mode 100644
index 00000000..50c4b3ec
--- /dev/null
+++ b/examples/single/newman/README.md
@@ -0,0 +1,92 @@
+# Newman Example
+
+This is a sample project demonstrating how to run Postman collections using the Newman CLI runner with integration to Qase Test Management.
+
+## Prerequisites
+
+Ensure that the following tools are installed on your machine:
+
+1. [Node.js](https://nodejs.org/) (version 18 or higher is recommended)
+2. [npm](https://www.npmjs.com/) or [yarn](https://yarnpkg.com/)
+
+## Setup Instructions
+
+1. Clone this repository by running the following commands:
+ ```bash
+ git clone https://github.com/qase-tms/qase-javascript.git
+ cd qase-javascript/examples/single/newman
+ ```
+
+2. Install the project dependencies:
+ ```bash
+ npm install
+ ```
+
+3. Create a `qase.config.json` file in the root of the project. Follow the instructions on [how to configure the file](https://github.com/qase-tms/qase-javascript/tree/main/qase-javascript-commons#configuration).
+
+## Example Files
+
+This example includes:
+
+* **sample-collection.json** — Postman collection with test assertions
+ * Contains examples with and without Qase ID comments
+ * Demonstrates comment-based test case linking
+* **qase.config.json** — Qase reporter configuration
+
+## Running Tests
+
+To run tests locally without reporting to Qase:
+
+```bash
+QASE_MODE=off npm test
+```
+
+To run tests and upload the results to Qase Test Management:
+
+```bash
+npm test
+```
+
+Or with explicit mode:
+
+```bash
+QASE_MODE=testops npx newman run sample-collection.json -r qase
+```
+
+## Expected Behavior
+
+When tests execute with Qase reporting enabled:
+
+* **Each pm.test()** in the collection is reported as a separate test result
+* **Comment-based IDs** (`// qase: 123`) link tests to existing Qase test cases
+* **Postman assertions** determine pass/fail status
+* **Collection structure** (folders) organizes tests in Qase
+* **Request/response data** is captured in test result details
+
+## Limitations
+
+Newman reporter has the following limitations compared to other frameworks:
+
+* **No programmatic steps API** — All assertions are reported at the test level (no qase.step())
+* **No programmatic attachments API** — No qase.attach() support (Postman security/portability constraint)
+* **No custom fields support** — Severity, priority, and other fields cannot be set via comments
+* **Comment-based only** — Test configuration uses special comments, not programmatic imports
+
+### Workarounds
+
+* **For step-like organization:** Use multiple `pm.test()` calls with descriptive names
+* **For attachments:** Use Postman console logging or store data in collection variables
+* **For test organization:** Use Postman collection folders to group related tests
+
+## Framework-Specific Features
+
+Newman with Qase has unique patterns:
+
+* **Comment-based annotations** — Use `// qase: 123` comments before `pm.test()` calls
+* **Data-driven testing** — Run with `-d data.json` for parameterized tests
+* **Multiple reporters** — Combine with other Newman reporters (`-r cli,qase`)
+* **Collection-level config** — Parameters can be specified at folder/collection level
+
+## Additional Resources
+
+For more details on how to use this integration with Qase Test Management, visit the [Qase Newman documentation](https://github.com/qase-tms/qase-javascript/tree/main/qase-newman).
diff --git a/examples/single/playwright/README.md b/examples/single/playwright/README.md
new file mode 100644
index 00000000..bf5baef1
--- /dev/null
+++ b/examples/single/playwright/README.md
@@ -0,0 +1,154 @@
+# Playwright Example
+
+This is a sample project demonstrating how to write and execute tests using the Playwright framework with integration to
+Qase Test Management.
+
+## Prerequisites
+
+Ensure that the following tools are installed on your machine:
+
+1. [Node.js](https://nodejs.org/) (version 18 or higher is recommended)
+2. [npm](https://www.npmjs.com/) or [yarn](https://yarnpkg.com/)
+
+## Setup Instructions
+
+1. Clone this repository by running the following commands:
+ ```bash
+ git clone https://github.com/qase-tms/qase-javascript.git
+ cd qase-javascript/examples/single/playwright
+ ```
+
+2. Install the project dependencies:
+ ```bash
+ npm install
+ ```
+
+3. Install Playwright browsers:
+ ```bash
+ npx playwright install
+ ```
+
+4. Create a `qase.config.json` file in the root of the project. Follow the instructions
+ on [how to configure the file](https://github.com/qase-tms/qase-javascript/tree/main/qase-javascript-commons#configuration).
+
+5. To run tests locally without Qase reporting:
+ ```bash
+ QASE_MODE=off npx playwright test
+ ```
+
+6. To run tests and upload the results to Qase Test Management:
+ ```bash
+ QASE_MODE=testops npx playwright test
+ ```
+
+## Example Files
+
+This project contains several test files demonstrating different Qase features:
+
+| File | Feature | Description |
+|------|---------|-------------|
+| `id.spec.js` | Test case linking | Links test to Qase test case by ID using `qase(id, name)` wrapper or `qase.id()` method |
+| `title.spec.js` | Custom titles | Sets custom test result titles with `qase.title()` |
+| `fields.spec.js` | Custom fields | Sets severity, priority, description, and other metadata with `qase.fields()` |
+| `suite.spec.js` | Suite organization | Groups tests into suites and sub-suites with `qase.suite()` |
+| `steps.spec.js` | Test steps | Defines execution steps with `await test.step()` (Playwright native) |
+| `chain.spec.js` | Method chaining | Demonstrates chaining multiple Qase methods |
+| `attach.spec.js` | Attachments | Attaches files and content to test results with `qase.attach()` |
+| `comment.spec.js` | Comments | Adds comments to test results with `qase.comment()` |
+| `ignore.spec.js` | Ignoring tests | Excludes tests from Qase reporting with `qase.ignore()` |
+| `params.spec.js` | Parameters | Reports parameterized test data with `qase.parameters()` |
+
+## Expected Behavior
+
+### Running with QASE_MODE=off (Local Development)
+
+When running tests with `QASE_MODE=off`, tests execute normally without Qase reporting:
+
+- Tests run and pass/fail as usual
+- No data is sent to Qase TestOps
+- No Qase API token required
+- Output shows standard Playwright test results
+- Playwright trace viewer and reports work normally
+
+This mode is useful for local development and debugging.
+
+### Running with QASE_MODE=testops (CI/CD and Reporting)
+
+When running tests with `QASE_MODE=testops`, test results are reported to Qase:
+
+- Tests execute and results are sent to Qase TestOps
+- A new test run is created in your Qase project
+- Test results include all metadata (steps, attachments, fields, etc.)
+- Console output includes Qase test run link
+- Requires valid `QASE_TESTOPS_API_TOKEN` and `QASE_TESTOPS_PROJECT` configuration
+- Playwright's native screenshots and traces can be attached automatically
+
+**Steps Example (`steps.spec.js`):**
+- Uses Playwright's native `test.step()` which is automatically reported to Qase
+- Each step shows execution status, duration, and any errors
+- Nested steps appear hierarchically in Qase
+- Steps are also visible in Playwright's trace viewer
+
+**Attachments Example (`attach.spec.js`):**
+- Files attached via `paths` option appear in test results
+- Screenshots captured with `page.screenshot()` can be attached
+- Content attached via `content` option is uploaded to Qase
+- Attachments are visible in the test run details
+- Supports text, JSON, images, videos, and binary files
+
+**Multi-Project Support:**
+- When configured for multi-project reporting, same test results are sent to multiple Qase projects
+- Each project can have different test case IDs for the same test
+
+## Configuration
+
+Example `qase.config.json`:
+
+```json
+{
+ "mode": "testops",
+ "debug": false,
+ "testops": {
+ "api": {
+ "token": "your_api_token_here"
+ },
+ "project": "YOUR_PROJECT_CODE",
+ "run": {
+ "title": "Playwright Automated Test Run",
+ "complete": true
+ }
+ }
+}
+```
+
+Or configure via `playwright.config.ts`:
+
+```typescript
+import { defineConfig } from '@playwright/test';
+
+export default defineConfig({
+ reporter: [
+ ['list'],
+ [
+ 'playwright-qase-reporter',
+ {
+ mode: 'testops',
+ testops: {
+ api: {
+ token: process.env.QASE_TESTOPS_API_TOKEN,
+ },
+ project: 'YOUR_PROJECT_CODE',
+ run: {
+ complete: true,
+ },
+ },
+ },
+ ],
+ ],
+});
+```
+
+## Additional Resources
+
+For more details on how to use this integration with Qase Test Management, visit
+the [Qase Playwright documentation](https://github.com/qase-tms/qase-javascript/tree/main/qase-playwright).
diff --git a/examples/single/testcafe/README.md b/examples/single/testcafe/README.md
new file mode 100644
index 00000000..d0951cff
--- /dev/null
+++ b/examples/single/testcafe/README.md
@@ -0,0 +1,82 @@
+# TestCafe Example
+
+This is a sample project demonstrating how to write and execute tests using the TestCafe framework with integration to Qase Test Management.
+
+## Prerequisites
+
+Ensure that the following tools are installed on your machine:
+
+1. [Node.js](https://nodejs.org/) (version 18 or higher is recommended)
+2. [npm](https://www.npmjs.com/) or [yarn](https://yarnpkg.com/)
+
+## Setup Instructions
+
+1. Clone this repository by running the following commands:
+ ```bash
+ git clone https://github.com/qase-tms/qase-javascript.git
+ cd qase-javascript/examples/single/testcafe
+ ```
+
+2. Install the project dependencies:
+ ```bash
+ npm install
+ ```
+
+3. Create a `qase.config.json` file in the root of the project. Follow the instructions on [how to configure the file](https://github.com/qase-tms/qase-javascript/tree/main/qase-javascript-commons#configuration).
+
+## Example Files
+
+This example includes:
+
+* **simpleTests.js** — Basic test examples demonstrating:
+ * Tests with and without Qase metadata
+ * Builder pattern usage: `test.meta(qase.id(1).create())`
+ * Setting title, fields, and ignore flags
+* **attachmentTests.js** — Attachment examples showing:
+ * File attachments from paths
+ * Content attachments from memory using `type` parameter
+ * Step-level attachments
+* **qase.config.json** — Qase reporter configuration
+
+## Running Tests
+
+To run tests locally without reporting to Qase:
+
+```bash
+QASE_MODE=off npm test
+```
+
+To run tests and upload the results to Qase Test Management:
+
+```bash
+npm test
+```
+
+Or with explicit mode and browser:
+
+```bash
+QASE_MODE=testops npx testcafe chrome simpleTests.js
+```
+
+## Expected Behavior
+
+When tests execute with Qase reporting enabled:
+
+* **Tests with .meta(qase.id().create())** are linked to existing Qase test cases
+* **Builder pattern chaining** allows combining metadata: `qase.id(1).title('...').fields({...}).create()`
+* **Attachments use 'type' parameter** for MIME type specification (not 'contentType')
+* **Steps with nested callbacks** provide hierarchical test organization
+* **TestCafe's async/await** syntax works seamlessly with Qase reporter
+
+## Framework-Specific Features
+
+TestCafe with Qase has unique patterns:
+
+* **Builder pattern** — Use `.meta(qase.id().create())` to attach metadata to tests
+* **Type parameter for attachments** — `qase.attach({ name: 'file.txt', content: '...', type: 'text/plain' })`
+* **Nested steps via callbacks** — Use step parameter (s, s1, s2) for nesting: `await qase.step('Parent', async (s1) => { await s1.step('Child', ...) })`
+* **No wrapper function** — TestCafe uses `.meta()` API instead of wrapping test declarations
+
+## Additional Resources
+
+For more details on how to use this integration with Qase Test Management, visit the [Qase TestCafe documentation](https://github.com/qase-tms/qase-javascript/tree/main/qase-testcafe).
diff --git a/examples/single/vitest/README.md b/examples/single/vitest/README.md
index 88a737ab..8d99d0f1 100644
--- a/examples/single/vitest/README.md
+++ b/examples/single/vitest/README.md
@@ -16,7 +16,7 @@ Ensure that the following tools are installed on your machine:
```bash
git clone https://github.com/qase-tms/qase-javascript.git
- cd qase-js/examples/vitest
+ cd qase-javascript/examples/single/vitest
```
2. Install the project dependencies:
@@ -26,16 +26,140 @@ Ensure that the following tools are installed on your machine:
```
3. Configure your Qase project settings:
- - Update the API token in `vitest.config.ts`
+ - Update the API token in `vitest.config.ts` or create a `qase.config.json` file
- Set the correct project name
- Enable "Create test cases" option in your Qase project settings
-4. To run tests and upload the results to Qase Test Management, use the following command:
+4. To run tests locally without Qase reporting:
```bash
- npm test
+ QASE_MODE=off npm test
```
+5. To run tests and upload the results to Qase Test Management:
+
+ ```bash
+ QASE_MODE=testops npm test
+ ```
+
+## Example Files
+
+This project contains several test files demonstrating different Qase features:
+
+| File | Feature | Description |
+|------|---------|-------------|
+| `id.test.ts` | Test case linking | Links test to Qase test case by ID using `qase(id, name)` wrapper |
+| `title.test.ts` | Custom titles | Sets custom test result titles with `qase.title()` |
+| `fields.test.ts` | Custom fields | Sets severity, priority, description, and other metadata with `qase.fields()` |
+| `suite.test.ts` | Suite organization | Groups tests into suites and sub-suites with `qase.suite()` |
+| `steps.test.ts` | Test steps | Defines execution steps with `await qase.step()` using `withQase()` wrapper |
+| `attach.test.ts` | Attachments | Attaches files and content to test results with `qase.attach()` |
+| `comment.test.ts` | Comments | Adds comments to test results with `qase.comment()` |
+| `params.test.ts` | Parameters | Reports parameterized test data with `qase.parameters()` |
+| `api.test.ts` | API testing | Demonstrates API testing with Qase integration |
+| `e2e.test.ts` | E2E testing | End-to-end testing example with Qase reporting |
+| `for.test.ts` | Loop tests | Demonstrates using loops to generate multiple tests |
+
+## Expected Behavior
+
+### Running with QASE_MODE=off (Local Development)
+
+When running tests with `QASE_MODE=off`, tests execute normally without Qase reporting:
+
+- Tests run and pass/fail as usual
+- No data is sent to Qase TestOps
+- No Qase API token required
+- Output shows standard Vitest test results
+- Vitest UI and watch mode work normally
+
+This mode is useful for local development and debugging.
+
+### Running with QASE_MODE=testops (CI/CD and Reporting)
+
+When running tests with `QASE_MODE=testops`, test results are reported to Qase:
+
+- Tests execute and results are sent to Qase TestOps
+- A new test run is created in your Qase project
+- Test results include all metadata (steps, attachments, fields, etc.)
+- Console output includes Qase test run link
+- Requires valid `QASE_TESTOPS_API_TOKEN` and `QASE_TESTOPS_PROJECT` configuration
+
+**Steps Example (`steps.test.ts`):**
+- Uses `withQase(async ({ qase }) => { ... })` wrapper to access step functionality
+- Creates test result with multiple named steps using `await qase.step()`
+- Each step shows execution status, duration, and any errors
+- Nested steps appear hierarchically in Qase
+- Steps with expected results and data are captured
+
+**Attachments Example (`attach.test.ts`):**
+- Uses `withQase(async ({ qase }) => { ... })` wrapper to access attach functionality
+- Files attached via `paths` option appear in test results
+- Content attached via `content` option is uploaded to Qase
+- **Note:** Vitest uses `type:` parameter instead of `contentType:` for in-memory attachments
+- Attachments are visible in the test run details
+- Supports text, JSON, images, and binary files
+
+**Multi-Project Support:**
+- When configured for multi-project reporting, same test results are sent to multiple Qase projects
+- Each project can have different test case IDs for the same test
+
+## Configuration
+
+Example `qase.config.json`:
+
+```json
+{
+ "mode": "testops",
+ "debug": false,
+ "testops": {
+ "api": {
+ "token": "your_api_token_here"
+ },
+ "project": "YOUR_PROJECT_CODE",
+ "run": {
+ "title": "Vitest Automated Test Run",
+ "complete": true
+ }
+ }
+}
+```
+
+Or configure via `vitest.config.ts`:
+
+```typescript
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ test: {
+ reporters: [
+ 'default',
+ [
+ 'vitest-qase-reporter',
+ {
+ mode: 'testops',
+ testops: {
+ api: {
+ token: process.env.QASE_TESTOPS_API_TOKEN,
+ },
+ project: 'YOUR_PROJECT_CODE',
+ run: {
+ complete: true,
+ },
+ },
+ },
+ ],
+ ],
+ },
+});
+```
+
+## Important Notes
+
+- **withQase Wrapper:** For tests using steps or attachments, wrap your test callback with `withQase(async ({ qase }) => { ... })` to access the `qase` object
+- **Import Pattern:** Use `import { withQase } from 'vitest-qase-reporter/vitest';` for step/attach functionality
+- **Attachment Parameter:** Use `type:` instead of `contentType:` when attaching content from memory
+- **TypeScript Support:** Vitest examples use TypeScript (.ts files) with full type safety
+
## Additional Resources
For more details on how to use this integration with Qase Test Management, visit
diff --git a/qase-cucumberjs/README.md b/qase-cucumberjs/README.md
index f11f47d3..63092194 100644
--- a/qase-cucumberjs/README.md
+++ b/qase-cucumberjs/README.md
@@ -1,142 +1,256 @@
-# Qase TMS Cucumber JS reporter
+# [Qase TestOps](https://qase.io) CucumberJS Reporter
-Publish results simple and easy.
+[](https://www.apache.org/licenses/LICENSE-2.0)
+[](https://www.npmjs.com/package/cucumberjs-qase-reporter)
-To install the latest version, run:
+Qase CucumberJS Reporter enables seamless integration between your CucumberJS tests and [Qase TestOps](https://qase.io), providing automatic test result reporting, test case management, and comprehensive test analytics.
-```sh
-npm install -D cucumberjs-qase-reporter
-```
-
-## Updating from v1
+## Features
-To update a test project using cucumberjs-qase-reporter@v1 to version 2:
+- Link automated tests to Qase test cases by ID using Gherkin tags
+- Auto-create test cases from your Gherkin scenarios
+- Report test results with rich metadata (fields, attachments, steps)
+- Native Gherkin step reporting (Given/When/Then automatically mapped)
+- Multi-project reporting support
+- Flexible configuration (file, environment variables, CucumberJS formatter)
-1. Update reporter configuration in `qase.config.json` and/or environment variables —
- see the [configuration reference](#configuration) below.
+## Installation
-## Getting started
+```sh
+npm install --save-dev cucumberjs-qase-reporter
+```
-The Cucumber JS reporter can auto-generate test cases
-and suites from your test data.
-Test results of subsequent test runs will match the same test cases
-as long as their names and file paths don't change.
+## Quick Start
-You can also annotate the tests with the IDs of existing test cases
-from Qase.io before executing tests. It's a more reliable way to bind
-autotests to test cases, that persists when you rename, move, or
-parameterize your tests.
+**1. Create `qase.config.json` in your project root:**
-For more information, see the [Usage Guide](docs/usage.md).
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "project": "YOUR_PROJECT_CODE",
+ "api": {
+ "token": "YOUR_API_TOKEN"
+ }
+ }
+}
+```
-For example:
+**2. Add Qase ID tag to your Gherkin scenario:**
```gherkin
-Feature: Cucumber documentation
- As a user of cucumber.js
- I want to have documentation on cucumber
- So I can write better applications
+Feature: User Authentication
@QaseID=1
- Scenario: Usage documentation
- Given I am on the cucumber.js GitHub repository
- When I go to the README file
- Then I should see a "Cool" section
-
- @QaseID=2
- @QaseFields={'severity':'high'}
- Scenario: Status badges 2
- Given I am on the cucumber.js GitHub repository
- When I go to the README file
- Then I should see a "Build Status" badge
- And I should see a "Dependencies" badge
+ Scenario: Successful login
+ Given I am on the login page
+ When I enter valid credentials
+ Then I should be logged in
```
-To execute Cucumber JS tests and report them to Qase.io, run the command:
+**3. Run your tests with the reporter:**
-```bash
-QASE_MODE=testops cucumber-js -f cucumberjs-qase-reporter features -r step_definitions --publish-quiet
+```sh
+QASE_MODE=testops npx cucumber-js -f cucumberjs-qase-reporter
```
-or
+## Configuration
-```bash
-npm test
-```
+The reporter is configured via (in order of priority):
-You can try it with the example project at [`examples/cucumberjs`](../examples/cucumberjs/).
+1. **CucumberJS CLI flags** (highest priority)
+2. **Environment variables** (`QASE_*`)
+3. **Config file** (`qase.config.json`)
-
-
-
+### CucumberJS Formatter Configuration
-A test run will be performed and available at:
+Run CucumberJS with the Qase formatter:
+```sh
+npx cucumber-js -f cucumberjs-qase-reporter features -r step_definitions
```
-https://app.qase.io/run/QASE_PROJECT_CODE
-```
-
-
-
-
-
-### Multi-Project Support
-
-Qase CucumberJS Reporter supports sending test results to multiple Qase projects simultaneously. Use tags in feature files: `@qaseid.PROJ1(1) @qaseid.PROJ2(2)`.
-
-For detailed information, configuration, and examples, see the [Multi-Project Support Guide](docs/MULTI_PROJECT.md).
-## Configuration
-
-Qase Cucumber JS reporter can be configured in multiple ways:
+Or configure in your `cucumber.js` profile:
+
+```javascript
+// cucumber.js
+module.exports = {
+ default: {
+ format: ['progress', 'cucumberjs-qase-reporter'],
+ requireModule: ['ts-node/register'],
+ require: ['step_definitions/**/*.js'],
+ },
+};
+```
-- using a separate config file `qase.config.json`,
-- using environment variables (they override the values from the configuration files).
+### Minimal Configuration
-For a full list of configuration options, see
-the [Configuration reference](../qase-javascript-commons/README.md#configuration).
+| Option | Environment Variable | Description |
+|--------|---------------------|-------------|
+| `mode` | `QASE_MODE` | Set to `testops` to enable reporting |
+| `testops.project` | `QASE_TESTOPS_PROJECT` | Your Qase project code |
+| `testops.api.token` | `QASE_TESTOPS_API_TOKEN` | Your Qase API token |
-Example `qase.config.json` file:
+### Example `qase.config.json`
```json
{
"mode": "testops",
- "debug": true,
+ "fallback": "report",
"testops": {
+ "project": "YOUR_PROJECT_CODE",
"api": {
- "token": "api_key"
+ "token": "YOUR_API_TOKEN"
},
- "project": "project_code",
"run": {
- "complete": true
+ "title": "CucumberJS Automated Run"
+ },
+ "batch": {
+ "size": 100
+ }
+ },
+ "report": {
+ "driver": "local",
+ "connection": {
+ "local": {
+ "path": "./build/qase-report",
+ "format": "json"
+ }
}
}
}
```
-Supported ENV variables:
+> **Full configuration reference:** See [qase-javascript-commons](../qase-javascript-commons/README.md) for all available options including logging, status mapping, execution plans, and more.
+
+## Usage
+
+### Link Tests with Test Cases
+
+Associate your scenarios with Qase test cases using Gherkin tags:
+
+**Single ID:**
+```gherkin
+Feature: User Authentication
+
+ @QaseID=1
+ Scenario: Valid login
+ Given I am on the login page
+ When I enter valid credentials
+ Then I should see the dashboard
+```
+
+**Multiple IDs:**
+```gherkin
+Feature: User Authentication
+
+ @QaseID=1,2,3
+ Scenario: Multiple test case coverage
+ Given I am on the login page
+ When I enter valid credentials
+ Then I should see the dashboard
+```
+
+> **Note:** Unlike other frameworks, CucumberJS uses Gherkin tags (`@QaseID=N`) instead of programmatic wrapper functions. Test case linking happens at the scenario level in feature files.
-- `QASE_MODE` - Same as `mode`
-- `QASE_DEBUG` - Same as `debug`
-- `QASE_ENVIRONMENT` - Same as `environment`
-- `QASE_TESTOPS_API_TOKEN` - Same as `testops.api.token`
-- `QASE_TESTOPS_PROJECT` - Same as `testops.project`
-- `QASE_TESTOPS_RUN_ID` - Pass Run ID from ENV and override reporter option `testops.run.id`
-- `QASE_TESTOPS_RUN_TITLE` - Same as `testops.run.title`
-- `QASE_TESTOPS_RUN_DESCRIPTION` - Same as `testops.run.description`
+### Add Metadata
-To run using ENV you have to execute:
+Enhance your scenarios with additional information using tags:
-```bash
-cucumber-js -f cucumberjs-qase-reporter features -r step_definitions --publish-quiet
+**Using Gherkin Tags:**
+```gherkin
+Feature: User Authentication
+
+ @QaseID=1
+ @QaseTitle=Custom Test Title
+ @QaseFields={"severity":"critical","priority":"high","layer":"e2e"}
+ Scenario: Login with metadata
+ Given I am on the login page
+ When I enter valid credentials
+ Then I should see the dashboard
```
+**Programmatic Metadata (in Before hooks):**
+```javascript
+// support/hooks.js
+const { Before } = require('@cucumber/cucumber');
+
+Before(function() {
+ // Note: programmatic metadata requires custom implementation
+ // Most metadata is set via Gherkin tags
+});
+```
+
+### Ignore Tests
+
+Exclude specific scenarios from Qase reporting (scenario still runs, but results are not sent):
+
+```gherkin
+Feature: User Authentication
+
+ @QaseIgnore
+ Scenario: Test not reported to Qase
+ Given I am on the login page
+ When I enter valid credentials
+ Then I should see the dashboard
+```
+
+### Test Result Statuses
+
+| CucumberJS Result | Qase Status |
+|-------------------|-------------|
+| Passed | Passed |
+| Failed | Failed |
+| Pending | Blocked |
+| Skipped | Skipped |
+| Undefined | Blocked |
+| Ambiguous | Failed |
+
+> For more usage examples, see the [Usage Guide](docs/usage.md).
+
+## Running Tests
+
+**Basic test execution with reporter:**
+```sh
+QASE_MODE=testops npx cucumber-js -f cucumberjs-qase-reporter
+```
+
+**With specific features:**
+```sh
+QASE_MODE=testops npx cucumber-js -f cucumberjs-qase-reporter features/login.feature
+```
+
+**With tags filtering:**
+```sh
+npx cucumber-js -f cucumberjs-qase-reporter --tags "@smoke and not @skip"
+```
+
+**Using cucumber.js profile:**
+```sh
+npx cucumber-js --profile default
+```
+
+> **Note:** The reporter formatter should be specified with `-f cucumberjs-qase-reporter` flag or in your cucumber.js configuration.
+
## Requirements
-We maintain the reporter on [LTS versions of Node](https://nodejs.org/en/about/releases/).
+- Node.js >= 14
+- @cucumber/cucumber >= 8.0.0
+
+## Documentation
+
+| Guide | Description |
+|-------|-------------|
+| [Usage Guide](docs/usage.md) | Complete usage reference with all tags and patterns |
+| [Attachments](docs/ATTACHMENTS.md) | Adding screenshots, logs, and files to test results |
+| [Steps](docs/STEPS.md) | Understanding native Gherkin step mapping |
+| [Multi-Project Support](docs/MULTI_PROJECT.md) | Reporting to multiple Qase projects |
+| [Upgrade Guide](docs/UPGRADE.md) | Migration guide for breaking changes |
+
+## Examples
-`@cucumber/cucumber >= 7.0.0`
+See the [examples directory](../examples/) for complete working examples.
-
+## License
-[auth]: https://developers.qase.io/#authentication
+Apache License 2.0. See [LICENSE](LICENSE) for details.
diff --git a/qase-cucumberjs/docs/ATTACHMENTS.md b/qase-cucumberjs/docs/ATTACHMENTS.md
new file mode 100644
index 00000000..1f8eabc8
--- /dev/null
+++ b/qase-cucumberjs/docs/ATTACHMENTS.md
@@ -0,0 +1,337 @@
+# Attachments in CucumberJS
+
+This guide covers how to attach files, screenshots, logs, and other content to your Qase test results when using CucumberJS.
+
+---
+
+## Overview
+
+CucumberJS reporter captures attachments using **Cucumber's native attachment API** (`this.attach()`) within step definitions. The Qase reporter automatically captures these attachments and includes them in your test results.
+
+**Key difference from other frameworks:** CucumberJS does not use `qase.attach()` programmatically. Instead, use Cucumber's built-in `this.attach()` method in your step definitions.
+
+Attachments are automatically associated with:
+- **Test results** — When added in step definitions
+- **Specific steps** — Attachments are linked to the Gherkin step they're added in
+
+---
+
+## Limitations
+
+CucumberJS uses Cucumber's native attachment API (`this.attach()`) within step definitions. The Qase reporter automatically captures these attachments. There is no programmatic `qase.attach()` import from the reporter package.
+
+---
+
+## Attaching Files
+
+### From File Path
+
+```javascript
+const { Given, When, Then } = require('@cucumber/cucumber');
+const fs = require('fs');
+
+When('I attach a log file', async function() {
+ const fileContent = fs.readFileSync('path/to/file.txt');
+ this.attach(fileContent, 'text/plain');
+});
+```
+
+### Multiple Files
+
+```javascript
+const { Given, When, Then } = require('@cucumber/cucumber');
+const fs = require('fs');
+
+When('I attach multiple files', async function() {
+ const file1 = fs.readFileSync('path/to/file1.txt');
+ const file2 = fs.readFileSync('path/to/file2.log');
+
+ this.attach(file1, 'text/plain');
+ this.attach(file2, 'text/plain');
+});
+```
+
+---
+
+## Attaching Content from Memory
+
+### Text Content
+
+```javascript
+const { Given, When, Then } = require('@cucumber/cucumber');
+
+When('I log test execution details', function() {
+ const logContent = 'Test execution log content';
+ this.attach(logContent, 'text/plain');
+});
+```
+
+### Binary Content (Screenshots)
+
+```javascript
+const { Given, When, Then } = require('@cucumber/cucumber');
+
+When('I take a screenshot', async function() {
+ // Example using WebDriver or similar
+ const screenshot = await this.driver.takeScreenshot();
+ this.attach(Buffer.from(screenshot, 'base64'), 'image/png');
+});
+```
+
+### JSON Data
+
+```javascript
+const { Given, When, Then } = require('@cucumber/cucumber');
+
+When('I attach test data', function() {
+ const testData = {
+ userId: 123,
+ username: 'testuser',
+ timestamp: new Date().toISOString(),
+ };
+
+ this.attach(JSON.stringify(testData, null, 2), 'application/json');
+});
+```
+
+---
+
+## Attaching in Step Definitions
+
+Attachments are added within step definitions and automatically linked to the Gherkin step:
+
+```gherkin
+Feature: User Login
+
+ Scenario: Successful login with screenshot
+ Given I am on the login page
+ When I enter valid credentials
+ And I take a screenshot
+ Then I should see the dashboard
+```
+
+```javascript
+const { Given, When, Then } = require('@cucumber/cucumber');
+
+Given('I am on the login page', async function() {
+ await this.driver.get('https://example.com/login');
+});
+
+When('I enter valid credentials', async function() {
+ await this.driver.findElement({ id: 'email' }).sendKeys('user@example.com');
+ await this.driver.findElement({ id: 'password' }).sendKeys('password123');
+});
+
+When('I take a screenshot', async function() {
+ const screenshot = await this.driver.takeScreenshot();
+ this.attach(Buffer.from(screenshot, 'base64'), 'image/png');
+});
+
+Then('I should see the dashboard', async function() {
+ const url = await this.driver.getCurrentUrl();
+ assert.strictEqual(url, 'https://example.com/dashboard');
+});
+```
+
+---
+
+## Method Reference
+
+### `this.attach()`
+
+Cucumber's native method to attach content to test results.
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `data` | `string` or `Buffer` | Yes | Content to attach |
+| `mediaType` | `string` | Yes | MIME type of the content |
+
+**Usage:**
+```javascript
+const { When } = require('@cucumber/cucumber');
+
+When('I attach a file', function() {
+ this.attach('File content here', 'text/plain');
+});
+```
+
+**With Buffer:**
+```javascript
+When('I attach binary data', function() {
+ const buffer = Buffer.from('binary data');
+ this.attach(buffer, 'application/octet-stream');
+});
+```
+
+---
+
+## MIME Types
+
+Common MIME types for attachments:
+
+| Content Type | MIME Type |
+|-------------|-----------|
+| PNG image | `image/png` |
+| JPEG image | `image/jpeg` |
+| GIF image | `image/gif` |
+| Plain text | `text/plain` |
+| JSON data | `application/json` |
+| XML data | `application/xml` |
+| HTML | `text/html` |
+| CSV | `text/csv` |
+| PDF | `application/pdf` |
+| ZIP | `application/zip` |
+
+---
+
+## Common Use Cases
+
+### Selenium/WebDriver Screenshots
+
+```javascript
+const { When } = require('@cucumber/cucumber');
+
+When('I capture a screenshot', async function() {
+ const screenshot = await this.driver.takeScreenshot();
+ this.attach(Buffer.from(screenshot, 'base64'), 'image/png');
+});
+```
+
+### Playwright Screenshots
+
+```javascript
+const { When } = require('@cucumber/cucumber');
+
+When('I take a page screenshot', async function() {
+ const screenshot = await this.page.screenshot();
+ this.attach(screenshot, 'image/png');
+});
+```
+
+### API Response Logs
+
+```javascript
+const { When } = require('@cucumber/cucumber');
+
+When('I make an API request', async function() {
+ const response = await fetch('https://api.example.com/users');
+ const data = await response.json();
+
+ this.attach(JSON.stringify(data, null, 2), 'application/json');
+});
+```
+
+### Console Output Logs
+
+```javascript
+const { When } = require('@cucumber/cucumber');
+
+When('I capture console output', function() {
+ const consoleOutput = this.capturedLogs.join('\n');
+ this.attach(consoleOutput, 'text/plain');
+});
+```
+
+### Page Source HTML
+
+```javascript
+const { When } = require('@cucumber/cucumber');
+
+When('I capture page source', async function() {
+ const pageSource = await this.driver.getPageSource();
+ this.attach(pageSource, 'text/html');
+});
+```
+
+---
+
+## Attaching on Failure
+
+Use Cucumber hooks to automatically attach screenshots or logs on test failure:
+
+```javascript
+const { After } = require('@cucumber/cucumber');
+
+After(async function(scenario) {
+ if (scenario.result.status === 'failed') {
+ // Attach screenshot on failure
+ const screenshot = await this.driver.takeScreenshot();
+ this.attach(Buffer.from(screenshot, 'base64'), 'image/png');
+
+ // Attach page source
+ const pageSource = await this.driver.getPageSource();
+ this.attach(pageSource, 'text/html');
+
+ // Attach console logs
+ const logs = await this.driver.manage().logs().get('browser');
+ this.attach(JSON.stringify(logs, null, 2), 'application/json');
+ }
+});
+```
+
+---
+
+## Troubleshooting
+
+### Attachments Not Appearing
+
+1. Verify you're using Cucumber's native `this.attach()` method
+2. Ensure step definitions use regular functions (not arrow functions) to preserve `this` context:
+ ```javascript
+ // Correct
+ When('I attach a file', function() {
+ this.attach('content', 'text/plain');
+ });
+
+ // Incorrect - arrow function loses 'this' context
+ When('I attach a file', () => {
+ this.attach('content', 'text/plain'); // Error!
+ });
+ ```
+3. Check that the CucumberJS formatter is properly configured:
+ ```bash
+ npx cucumber-js -f cucumberjs-qase-reporter
+ ```
+4. Enable debug logging to see attachment capture:
+ ```json
+ {
+ "debug": true
+ }
+ ```
+
+### Large Files
+
+Large attachments may slow down test execution. Consider:
+- Compressing logs before attaching
+- Using selective attachment (e.g., only on failure)
+- Setting reasonable size limits
+
+### Binary Data Issues
+
+When attaching binary data:
+- Use `Buffer.from()` for base64 strings
+- Specify correct MIME type for the content type
+
+### Arrow Functions Break Attachments
+
+Cucumber's `this.attach()` requires access to the World context. Always use regular functions in step definitions:
+
+```javascript
+// Correct - regular function preserves 'this'
+When('I attach data', function() {
+ this.attach('data', 'text/plain');
+});
+
+// Incorrect - arrow function loses 'this'
+When('I attach data', () => {
+ this.attach('data', 'text/plain'); // TypeError: Cannot read property 'attach' of undefined
+});
+```
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Steps Guide](STEPS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-cucumberjs/docs/MULTI_PROJECT.md b/qase-cucumberjs/docs/MULTI_PROJECT.md
index 81515e50..dd1ff2a4 100644
--- a/qase-cucumberjs/docs/MULTI_PROJECT.md
+++ b/qase-cucumberjs/docs/MULTI_PROJECT.md
@@ -6,6 +6,8 @@ Qase CucumberJS Reporter supports sending test results to multiple Qase projects
* Different projects track the same functionality with different test case IDs
* You want to maintain separate test runs for different environments or teams
+---
+
## Configuration
For detailed configuration options, refer to the [qase-javascript-commons README](../../qase-javascript-commons/README.md#multi-project-support).
@@ -14,6 +16,33 @@ For detailed configuration options, refer to the [qase-javascript-commons README
Set `mode` to `testops_multi` in your `qase.config.json` and add the `testops_multi` section with `default_project` and `projects`.
+**Example configuration:**
+
+```json
+{
+ "mode": "testops_multi",
+ "testops_multi": {
+ "default_project": "PROJ1",
+ "projects": [
+ {
+ "code": "PROJ1",
+ "api": {
+ "token": "your_api_token_for_proj1"
+ }
+ },
+ {
+ "code": "PROJ2",
+ "api": {
+ "token": "your_api_token_for_proj2"
+ }
+ }
+ ]
+ }
+}
+```
+
+---
+
## Using Tags in Feature Files
Map scenarios to projects and case IDs using tags in your `.feature` files.
@@ -29,6 +58,19 @@ Feature: Multi-project example
@qaseid.DEMO(2)
Scenario: Scenario reported to two projects
Given I have a step
+ When I perform an action
+ Then I should see a result
+```
+
+### Multiple IDs per project
+
+You can specify multiple test case IDs for a single project using comma-separated values:
+
+```gherkin
+ @qaseid.PROJ1(10,11)
+ @qaseid.PROJ2(20)
+ Scenario: Scenario with multiple cases per project
+ Given I have multiple test cases
```
### Single project (legacy)
@@ -45,31 +87,102 @@ For a single project you can still use the legacy tag format:
Scenarios with no `@qaseid` or `@QaseID` tags are sent to the `default_project` from your configuration. The result is sent without linking to a test case (no case ID).
+**Key points:**
+
+- Single project (legacy): `@QaseID=100`
+- Multi-project: `@qaseid.PROJ1(100)` and `@qaseid.PROJ2(200)` (multiple tags)
+- Multiple IDs per project: `@qaseid.PROJ1(10,11)` (comma-separated, no spaces)
+
+---
+
## Tag Format
* **Multi-project**: `@qaseid.PROJECT_CODE(id1,id2,...)` — project code must match `testops_multi.projects[].code`.
* **Legacy single-project**: `@QaseID=123` or `@qaseid(123)` — sent to `default_project` with that ID.
+---
+
## Important Notes
1. **Project codes must match**: The project code in `@qaseid.PROJ1(1)` must match a `code` in `testops_multi.projects`.
2. **Mode**: Set `mode` to `testops_multi` in `qase.config.json`.
3. **Default project**: Scenarios without any Qase tags are sent to `default_project` without a case ID.
4. **Multiple tags**: You can combine multiple `@qaseid.PROJ(ids)` tags to report one scenario to multiple projects.
+5. **API tokens**: Each project in `testops_multi.projects[]` can have its own API token for separate authentication.
+
+---
## Examples
See the [multi-project CucumberJS example](../../examples/multiProject/cucumberjs/) for a complete runnable setup.
+### Complete Example
+
+Here's a complete Gherkin feature file showing multi-project usage:
+
+```gherkin
+Feature: User Authentication
+ As a user
+ I want to authenticate
+ So I can access the application
+
+ @qaseid.PROJ1(1)
+ @qaseid.PROJ2(2)
+ Scenario: User can login successfully
+ Given I am on the login page
+ When I enter username "testuser"
+ And I enter password "password123"
+ And I click the login button
+ Then I should see the dashboard
+
+ @qaseid.PROJ1(10,11)
+ @qaseid.PROJ2(20)
+ Scenario: User registration works
+ Given I am on the registration page
+ When I enter email "newuser@example.com"
+ And I enter password "securepass456"
+ And I click the register button
+ Then I should see a confirmation message
+
+ @QaseID=50
+ Scenario: Password reset (single project - legacy)
+ Given I am on the login page
+ When I click "Forgot password"
+ And I enter email "user@example.com"
+ Then I should receive a reset email
+
+ Scenario: Public page loads (no Qase tracking)
+ Given I am on the homepage
+ Then I should see the welcome message
+```
+
+---
+
## Troubleshooting
-### Results not appearing in projects
+### Results Not Appearing in All Projects
+
+* Verify `mode` is `testops_multi` in `qase.config.json`
+* Check that project codes in tags (e.g. `@qaseid.DEVX(1)`) match `testops_multi.projects[].code` exactly (case-sensitive)
+* Ensure all projects are listed in `testops_multi.projects`
+* Ensure each project has a valid API token with write permissions
+
+### Wrong Test Cases Linked
+
+* Verify tag spelling and format: `@qaseid.PROJECT_CODE(id1,id2)` (no spaces after commas)
+* Check that test case IDs exist in the respective projects
+* Enable debug logging to see how the reporter parses tags
+
+### Default Project Not Working
+
+* For scenarios without tags, check the `default_project` setting
+* Ensure tag spelling and project codes match the configuration exactly
+* Verify the default project code matches one of the projects in the `projects` array
-* Verify `mode` is `testops_multi` in `qase.config.json`.
-* Check that project codes in tags (e.g. `@qaseid.DEVX(1)`) match `testops_multi.projects[].code` exactly (case-sensitive).
-* Ensure all projects are listed in `testops_multi.projects`.
+---
-### Scenarios sent to wrong project
+## See Also
-* For scenarios without tags, check the `default_project` setting.
-* Ensure tag spelling and project codes match the configuration exactly.
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Examples](../../examples/multiProject/cucumberjs/)
diff --git a/qase-cucumberjs/docs/STEPS.md b/qase-cucumberjs/docs/STEPS.md
new file mode 100644
index 00000000..8f9d2a5e
--- /dev/null
+++ b/qase-cucumberjs/docs/STEPS.md
@@ -0,0 +1,359 @@
+# Test Steps in CucumberJS
+
+This guide covers how test steps are reported in Qase when using CucumberJS.
+
+---
+
+## Overview
+
+In CucumberJS, test steps are defined using **Gherkin syntax** (Given/When/Then), not the `qase.step()` API. Each Gherkin step in your feature files is automatically reported as a Qase test step.
+
+**Key difference from other frameworks:** CucumberJS does not use programmatic `qase.step()` calls. Test steps come directly from your Gherkin scenarios and are automatically captured by the Qase reporter.
+
+Each reported step includes:
+- Step name (from Gherkin step text)
+- Step status (passed/failed/skipped)
+- Step duration
+- Attachments (if any added via `this.attach()`)
+- Error details (on failure)
+
+---
+
+## Gherkin Steps
+
+### Basic Step Structure
+
+Gherkin steps use Given/When/Then/And/But keywords to define test behavior:
+
+```gherkin
+Feature: User Authentication
+
+ Scenario: User can log in
+ Given I am on the login page
+ When I enter valid credentials
+ And I click the login button
+ Then I should see the dashboard
+ And I should see my username
+```
+
+Each line (Given, When, And, Then) is automatically reported as a separate step in Qase.
+
+### Step Definitions
+
+Step definitions map Gherkin steps to executable code:
+
+```javascript
+const { Given, When, Then } = require('@cucumber/cucumber');
+
+Given('I am on the login page', async function() {
+ await this.driver.get('https://example.com/login');
+});
+
+When('I enter valid credentials', async function() {
+ await this.driver.findElement({ id: 'email' }).sendKeys('user@example.com');
+ await this.driver.findElement({ id: 'password' }).sendKeys('password123');
+});
+
+When('I click the login button', async function() {
+ await this.driver.findElement({ id: 'login-button' }).click();
+});
+
+Then('I should see the dashboard', async function() {
+ const url = await this.driver.getCurrentUrl();
+ assert.strictEqual(url, 'https://example.com/dashboard');
+});
+
+Then('I should see my username', async function() {
+ const username = await this.driver.findElement({ id: 'username' }).getText();
+ assert.strictEqual(username, 'user@example.com');
+});
+```
+
+---
+
+## Step Parameters
+
+Gherkin supports parameterized steps with data passed from the feature file:
+
+```gherkin
+Feature: User Login
+
+ Scenario: Login with specific user
+ Given I am on the login page
+ When I login as "john@example.com" with password "secret123"
+ Then I should see "Welcome, John"
+```
+
+```javascript
+const { When, Then } = require('@cucumber/cucumber');
+
+When('I login as {string} with password {string}', async function(email, password) {
+ await this.driver.findElement({ id: 'email' }).sendKeys(email);
+ await this.driver.findElement({ id: 'password' }).sendKeys(password);
+ await this.driver.findElement({ id: 'login-button' }).click();
+});
+
+Then('I should see {string}', async function(expectedText) {
+ const text = await this.driver.findElement({ id: 'message' }).getText();
+ assert.strictEqual(text, expectedText);
+});
+```
+
+In Qase, the step will show: `When I login as "john@example.com" with password "secret123"`
+
+---
+
+## Scenario Outline with Examples
+
+Scenario Outlines allow running the same scenario multiple times with different data:
+
+```gherkin
+Feature: User Login
+
+ Scenario Outline: Login with different users
+ Given I am on the login page
+ When I login as "" with password ""
+ Then I should see ""
+
+ Examples:
+ | email | password | message |
+ | john@example.com | pass123 | Welcome, John |
+ | jane@example.com | secret456 | Welcome, Jane |
+ | admin@example.com | admin789 | Welcome, Admin |
+```
+
+Each row in the Examples table creates a separate test run in Qase, with steps showing the actual parameter values.
+
+---
+
+## Background Steps
+
+Background steps run before each scenario in a feature:
+
+```gherkin
+Feature: Shopping Cart
+
+ Background:
+ Given I am logged in as "customer@example.com"
+ And I have cleared my shopping cart
+
+ Scenario: Add item to cart
+ When I add "Laptop" to cart
+ Then my cart should contain 1 item
+
+ Scenario: Remove item from cart
+ Given I have added "Laptop" to cart
+ When I remove "Laptop" from cart
+ Then my cart should be empty
+```
+
+Background steps are reported for each scenario automatically.
+
+---
+
+## Nested Steps
+
+Gherkin does not support nested steps directly. Each Given/When/Then/And step is reported as a flat step in Qase results.
+
+If you need hierarchical organization, use multiple scenarios or scenario outlines within a feature:
+
+```gherkin
+Feature: User Registration
+
+ Scenario: Complete registration process
+ Given I am on the registration page
+ When I fill in personal details
+ And I fill in address details
+ And I accept terms and conditions
+ And I submit the registration form
+ Then I should see a success message
+```
+
+Each step is reported as a top-level step (not nested).
+
+---
+
+## Steps with Data Tables
+
+Gherkin supports data tables for passing structured data to steps:
+
+```gherkin
+Feature: User Management
+
+ Scenario: Create user with details
+ Given I am on the user creation page
+ When I create a user with the following details:
+ | Field | Value |
+ | First Name | John |
+ | Last Name | Doe |
+ | Email | john@example.com |
+ | Role | Administrator |
+ Then the user should be created successfully
+```
+
+```javascript
+const { When } = require('@cucumber/cucumber');
+
+When('I create a user with the following details:', async function(dataTable) {
+ const userData = dataTable.rowsHash();
+
+ await this.driver.findElement({ id: 'firstName' }).sendKeys(userData['First Name']);
+ await this.driver.findElement({ id: 'lastName' }).sendKeys(userData['Last Name']);
+ await this.driver.findElement({ id: 'email' }).sendKeys(userData['Email']);
+ await this.driver.findElement({ id: 'role' }).sendKeys(userData['Role']);
+ await this.driver.findElement({ id: 'submit' }).click();
+});
+```
+
+The data table is included in the step report.
+
+---
+
+## Steps with Doc Strings
+
+Doc strings allow passing multi-line text to steps:
+
+```gherkin
+Feature: API Testing
+
+ Scenario: Post JSON data
+ Given I have API credentials
+ When I send a POST request with body:
+ """
+ {
+ "name": "John Doe",
+ "email": "john@example.com",
+ "role": "admin"
+ }
+ """
+ Then the response should be successful
+```
+
+```javascript
+const { When } = require('@cucumber/cucumber');
+
+When('I send a POST request with body:', async function(docString) {
+ const response = await fetch('https://api.example.com/users', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: docString,
+ });
+
+ this.response = response;
+});
+```
+
+---
+
+## Step Status
+
+Steps automatically inherit status from execution:
+
+| Execution | Step Status |
+|-----------|-------------|
+| Step definition completes successfully | Passed |
+| Assertion fails or error thrown | Failed |
+| Step definition not found | Undefined (reported as Blocked) |
+| Step skipped due to previous failure | Skipped |
+| Ambiguous step definition match | Ambiguous (reported as Failed) |
+
+---
+
+## Common Patterns
+
+### Page Navigation Steps
+
+```gherkin
+Feature: Website Navigation
+
+ Scenario: Navigate through pages
+ Given I am on the home page
+ When I click on "Products" in the menu
+ And I click on "Laptops" category
+ Then I should be on the laptops page
+ And I should see at least 5 products
+```
+
+### Form Interaction Steps
+
+```gherkin
+Feature: Contact Form
+
+ Scenario: Submit contact form
+ Given I am on the contact page
+ When I fill in the following:
+ | Name | John Doe |
+ | Email | john@example.com |
+ | Subject | Product Inquiry |
+ | Message | I need information |
+ And I submit the form
+ Then I should see "Thank you for contacting us"
+```
+
+### API Testing Steps
+
+```gherkin
+Feature: REST API
+
+ Scenario: Get user details
+ Given I have a valid API token
+ When I send a GET request to "/api/users/123"
+ Then the response status should be 200
+ And the response should contain:
+ | Field | Value |
+ | id | 123 |
+ | email | user@example.com |
+```
+
+---
+
+## Troubleshooting
+
+### Steps Not Appearing
+
+1. Verify reporter is configured as Cucumber formatter:
+ ```bash
+ npx cucumber-js -f cucumberjs-qase-reporter
+ ```
+2. Check that feature files are properly formatted with valid Gherkin syntax
+3. Ensure step definitions are loaded:
+ ```bash
+ npx cucumber-js -r step_definitions features/
+ ```
+4. Enable debug logging to trace step capture:
+ ```json
+ {
+ "debug": true
+ }
+ ```
+
+### Undefined Steps
+
+If steps show as "Undefined" in Qase:
+1. Verify step definitions exist for all Gherkin steps
+2. Check step definition patterns match exactly (including quotes and data types)
+3. Run with `--dry-run` to see missing step definitions:
+ ```bash
+ npx cucumber-js --dry-run
+ ```
+
+### Ambiguous Steps
+
+If multiple step definitions match the same Gherkin step:
+1. Review step definition patterns to ensure uniqueness
+2. Run with `--dry-run` to identify ambiguous matches
+3. Refactor step definitions to be more specific
+
+### Steps Show Wrong Duration
+
+- Very fast steps may show 0ms duration
+- Async steps without proper `await` may report incorrect duration
+- Ensure all async operations use `async/await` correctly
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Attachments Guide](ATTACHMENTS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-cucumberjs/docs/UPGRADE.md b/qase-cucumberjs/docs/UPGRADE.md
new file mode 100644
index 00000000..79a4cb5a
--- /dev/null
+++ b/qase-cucumberjs/docs/UPGRADE.md
@@ -0,0 +1,329 @@
+# Upgrade Guide: CucumberJS Reporter
+
+This guide covers migration steps between major versions of the Qase CucumberJS Reporter.
+
+---
+
+## Version History
+
+| Version | Release Date | Node.js Support | Key Changes |
+|---------|--------------|-----------------|-------------|
+| 2.2.0 | January 2026 | >= 14 | Current stable release with improved Gherkin tag parsing |
+| 2.1.0 | December 2025 | >= 14 | Enhanced scenario support and metadata handling |
+| 2.0.0 | August 2025 | >= 14 | Complete rewrite with new architecture |
+
+---
+
+## Upgrading to 2.x
+
+### Breaking Changes
+
+The cucumberjs-qase-reporter started with the v2.x architecture, leveraging the unified qase-javascript-commons library for consistent reporting across all test frameworks. If you are using v2.x, you are already on the latest architecture.
+
+**No migration from a previous major version is required for cucumberjs-qase-reporter.**
+
+### Current Version Features
+
+Version 2.2.0 includes:
+
+- Full support for Qase TestOps API with batch result upload
+- Gherkin tag-based test case linking: `@QaseID=1`
+- Field annotations via tags: `@QaseFields={'severity':'high'}`
+- Native Gherkin step reporting (Given/When/Then/And)
+- Native attachment support via `this.attach()` in step definitions
+- Multi-project support for reporting to multiple Qase projects
+- Flexible configuration via `qase.config.json` or environment variables
+
+---
+
+## Configuration
+
+### Current Format (v2.x)
+
+Configuration uses the modern qase-javascript-commons format:
+
+**qase.config.json:**
+
+```json
+{
+ "mode": "testops",
+ "debug": false,
+ "testops": {
+ "api": {
+ "token": "api_token_here"
+ },
+ "project": "DEMO",
+ "run": {
+ "title": "CucumberJS Automated Run",
+ "description": "Test run from CI/CD pipeline",
+ "complete": true
+ },
+ "batch": {
+ "size": 100
+ }
+ }
+}
+```
+
+**Environment Variables:**
+
+```bash
+export QASE_MODE=testops
+export QASE_TESTOPS_API_TOKEN=your_api_token
+export QASE_TESTOPS_PROJECT=DEMO
+```
+
+**Command Line:**
+
+```bash
+QASE_MODE=testops npx cucumber-js --format cucumberjs-qase-reporter
+```
+
+---
+
+## Usage Pattern
+
+### Test Case Linking via Tags
+
+CucumberJS uses Gherkin tags instead of programmatic API for test annotations:
+
+```gherkin
+Feature: User Authentication
+ As a user
+ I want to log in to the application
+ So I can access my account
+
+ @QaseID=1
+ Scenario: Successful login with valid credentials
+ Given I am on the login page
+ When I enter valid credentials
+ And I click the login button
+ Then I should see the dashboard
+
+ @QaseID=2
+ @QaseFields={'severity':'high','priority':'critical'}
+ Scenario: Failed login with invalid credentials
+ Given I am on the login page
+ When I enter invalid credentials
+ And I click the login button
+ Then I should see an error message
+```
+
+### Field Annotations
+
+```gherkin
+@QaseFields={'severity':'high','priority':'medium','layer':'e2e'}
+Scenario: User can update profile
+ Given I am logged in
+ When I update my profile information
+ Then I should see the updated information
+```
+
+**Supported field formats:**
+
+- Single quotes: `@QaseFields={'severity':'high'}`
+- Double quotes: `@QaseFields={"severity":"high"}`
+- Mixed: `@QaseFields={'severity':"high","priority":'medium'}`
+
+---
+
+## Steps Reporting
+
+### Native Gherkin Steps
+
+CucumberJS automatically reports Gherkin steps (Given/When/Then/And) to Qase:
+
+```gherkin
+Scenario: User login flow
+ Given I am on the login page # Step 1 in Qase
+ When I enter username "user@test.com" # Step 2 in Qase
+ And I enter password "password123" # Step 3 in Qase
+ And I click the login button # Step 4 in Qase
+ Then I should see the dashboard # Step 5 in Qase
+```
+
+**No qase.step() API** - Use native Gherkin steps instead.
+
+---
+
+## Attachments
+
+### Using Native CucumberJS Attachments
+
+CucumberJS uses the native `this.attach()` method in step definitions:
+
+**Step definition:**
+
+```javascript
+const { Given, When, Then } = require('@cucumber/cucumber');
+
+When('I capture a screenshot', async function () {
+ const screenshot = await browser.takeScreenshot();
+
+ // Attach to Cucumber (and reported to Qase)
+ await this.attach(screenshot, 'image/png');
+});
+
+Then('I attach test logs', async function () {
+ const logContent = getTestLogs();
+
+ await this.attach(logContent, 'text/plain');
+});
+```
+
+**No qase.attach() API** - Use native `this.attach()` in step definitions instead.
+
+---
+
+## API Differences from Other Reporters
+
+CucumberJS reporter has a unique pattern compared to other Qase reporters:
+
+| Feature | Other Reporters | CucumberJS |
+|---------|----------------|------------|
+| Test case ID linking | `qase(id, 'name')` wrapper | `@QaseID=1` tag |
+| Fields | `qase.fields({...})` | `@QaseFields={...}` tag |
+| Steps | `qase.step()` or native framework steps | Native Gherkin steps only |
+| Attachments | `qase.attach()` | `this.attach()` in step definitions |
+| Titles | `qase.title()` | Scenario name (auto) |
+| Suites | `qase.suite()` | Feature name (auto) |
+
+**No programmatic Qase API** - Everything is tag-based or native CucumberJS.
+
+---
+
+## Compatibility Notes
+
+### Node.js Version Support
+
+- **Current (2.2.0):** Node.js >= 14
+
+### CucumberJS Version Support
+
+- **Current (2.2.0):** CucumberJS >= 7.0.0
+- Tested with CucumberJS 10.x
+
+### Framework Compatibility
+
+- ES Modules recommended
+- CommonJS supported
+- TypeScript support with full type definitions
+- Works with all CucumberJS formatters
+- Compatible with parallel execution
+
+---
+
+## Troubleshooting
+
+### Common Issues
+
+#### Issue: Reporter not running
+
+**Solution:** Ensure you're using the correct formatter flag:
+
+```bash
+# Correct
+QASE_MODE=testops npx cucumber-js -f cucumberjs-qase-reporter
+
+# Also correct (combined with other formatters)
+QASE_MODE=testops npx cucumber-js -f progress -f cucumberjs-qase-reporter
+```
+
+#### Issue: Test case IDs not recognized
+
+**Solution:** Check your Gherkin tag syntax:
+
+```gherkin
+# Correct
+@QaseID=1
+Scenario: Test scenario
+
+# Incorrect - missing equals sign
+@QaseID 1
+Scenario: Test scenario
+
+# Incorrect - spaces around equals
+@QaseID = 1
+Scenario: Test scenario
+```
+
+#### Issue: Fields not parsing correctly
+
+**Solution:** Ensure proper JSON syntax in the tag:
+
+```gherkin
+# Correct - single quotes for object
+@QaseFields={'severity':'high','priority':'medium'}
+
+# Also correct - double quotes
+@QaseFields={"severity":"high","priority":"medium"}
+
+# Incorrect - missing quotes
+@QaseFields={severity:high,priority:medium}
+```
+
+#### Issue: Attachments not appearing in Qase
+
+**Solution:** Use native CucumberJS `this.attach()` in step definitions:
+
+```javascript
+// Correct - in step definition
+When('I take a screenshot', async function () {
+ const screenshot = await takeScreenshot();
+ await this.attach(screenshot, 'image/png');
+});
+
+// Incorrect - qase.attach() doesn't exist in CucumberJS
+When('I take a screenshot', async function () {
+ const screenshot = await takeScreenshot();
+ qase.attach({ content: screenshot }); // Error: qase is not defined
+});
+```
+
+#### Issue: Configuration not recognized
+
+**Solution:** Verify your `qase.config.json` exists and has correct structure:
+
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "api": { "token": "your_token" },
+ "project": "YOUR_PROJECT_CODE"
+ }
+}
+```
+
+Or use environment variables:
+
+```bash
+export QASE_MODE=testops
+export QASE_TESTOPS_API_TOKEN=your_token
+export QASE_TESTOPS_PROJECT=YOUR_PROJECT_CODE
+```
+
+---
+
+## Getting Help
+
+If you encounter issues:
+
+1. Check the [GitHub Issues](https://github.com/qase-tms/qase-javascript/issues)
+2. Review the CHANGELOG
+3. Open a new issue with:
+ - Current version (2.2.0)
+ - CucumberJS version
+ - Error messages
+ - Configuration file (without sensitive data)
+ - Example feature file
+ - Steps to reproduce
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Multi-Project Support](MULTI_PROJECT.md)
+- CHANGELOG
+- [CucumberJS Documentation](https://cucumber.io/docs/cucumber/)
diff --git a/qase-cucumberjs/docs/usage.md b/qase-cucumberjs/docs/usage.md
index 8967dbb9..81e091c1 100644
--- a/qase-cucumberjs/docs/usage.md
+++ b/qase-cucumberjs/docs/usage.md
@@ -1,48 +1,76 @@
-# Qase Integration in Cucumber.js
+# Qase Integration in CucumberJS
-This guide demonstrates how to integrate Qase with Cucumber.js, providing instructions on how to add Qase IDs, titles,
-fields, suites, comments, and file attachments to your test cases.
+This guide provides comprehensive instructions for integrating Qase with CucumberJS using Gherkin tags and step definitions.
+> **Configuration:** For complete configuration reference including all available options, environment variables, and examples, see the [qase-javascript-commons README](../../qase-javascript-commons/README.md).
+
+---
+
+## Table of Contents
+
+- [Adding QaseID](#adding-qaseid)
+- [Adding Title](#adding-title)
+- [Adding Fields](#adding-fields)
+- [Adding Suite](#adding-suite)
+- [Ignoring Tests](#ignoring-tests)
+- [Muting Tests](#muting-tests)
+- [Working with Attachments](#working-with-attachments)
+- [Working with Steps](#working-with-steps)
+- [Working with Parameters](#working-with-parameters)
+- [Multi-Project Support](#multi-project-support)
+- [Running Tests](#running-tests)
+- [Integration Patterns](#integration-patterns)
+- [Common Use Cases](#common-use-cases)
+- [Troubleshooting](#troubleshooting)
+- [Complete Examples](#complete-examples)
+
+- [See Also](#see-also)
---
-## Adding QaseID to a Test
+## Adding QaseID
-To associate a QaseID with a test in Cucumber.js, use the `@QaseId` tag in your Gherkin feature files. This tag accepts
-a single integer or multiple integers separated by commas representing the test"s ID(s) in Qase.
+Link your Gherkin scenarios to existing test cases in Qase using the `@QaseID` tag.
-### Example
+### Single ID
```gherkin
Feature: User Authentication
- @QaseId=1
+ @QaseID=1
Scenario: Successful login
Given I am on the login page
When I enter valid credentials
Then I should be logged in
+```
+
+### Multiple IDs
+
+```gherkin
+Feature: User Authentication
- @QaseId=2,3,4
- Scenario: Multiple test cases
+ @QaseID=1,2,3
+ Scenario: Login covering multiple test cases
Given I am on the login page
- When I enter invalid credentials
- Then I should see an error message
+ When I enter valid credentials
+ Then I should see the dashboard
```
----
+### Multi-Project Support
+
+To send test results to multiple Qase projects simultaneously, see the [Multi-Project Support Guide](MULTI_PROJECT.md).
-## Adding a Title to a Test
+---
-You can provide a custom title for your test using the `@Title` tag. The tag accepts a string, which will be used as
-the test"s title in Qase. If no title is provided, the scenario name will be used by default.
+## Adding Title
-### Example
+Set a custom title for the test case using the `@QaseTitle` tag (overrides scenario name):
```gherkin
Feature: User Authentication
- @QaseId=1
- @Title=Custom Test Title
- Scenario: Successful login
+ @QaseID=1
+ @QaseTitle=User can successfully complete login flow
+ Scenario: Login
Given I am on the login page
When I enter valid credentials
Then I should be logged in
@@ -50,28 +78,29 @@ Feature: User Authentication
---
-## Adding Fields to a Test
+## Adding Fields
-The `@QaseFields` tag allows you to add additional metadata to a test case. You can specify multiple fields to
-enhance test case information in Qase.
+Add metadata to your test cases using the `@QaseFields` tag. Both system and custom fields are supported.
### System Fields
-- `description` — Description of the test case.
-- `preconditions` — Preconditions for the test case.
-- `postconditions` — Postconditions for the test case.
-- `severity` — Severity of the test case (e.g., `critical`, `major`).
-- `priority` — Priority of the test case (e.g., `high`, `low`).
-- `layer` — Test layer (e.g., `UI`, `API`).
+| Field | Description | Example Values |
+|-------|-------------|----------------|
+| `description` | Test case description | Any text |
+| `preconditions` | Test preconditions | Any text (supports Markdown) |
+| `postconditions` | Test postconditions | Any text |
+| `severity` | Test severity | `blocker`, `critical`, `major`, `normal`, `minor`, `trivial` |
+| `priority` | Test priority | `high`, `medium`, `low` |
+| `layer` | Test layer | `e2e`, `api`, `unit` |
### Example
```gherkin
Feature: User Authentication
- @QaseId=1
- @QaseFields={"severity":"high","priority":"medium","description":"Login functionality test"}
- Scenario: Successful login
+ @QaseID=1
+ @QaseFields={"severity":"critical","priority":"high","layer":"e2e","description":"Tests core authentication flow"}
+ Scenario: Critical login test
Given I am on the login page
When I enter valid credentials
Then I should be logged in
@@ -79,45 +108,51 @@ Feature: User Authentication
---
-## Adding a Suite to a Test
+## Adding Suite
-To assign a suite or sub-suite to a test, use the `@QaseSuite` tag. It can receive a suite name, and optionally a
-sub-suite, both as strings.
+Organize scenarios into suites using the `@QaseSuite` tag:
-### Example
+### Simple Suite
```gherkin
Feature: User Authentication
- @QaseId=1
+ @QaseID=1
@QaseSuite=Authentication
- Scenario: Successful login
+ Scenario: Login test
Given I am on the login page
When I enter valid credentials
Then I should be logged in
+```
- @QaseId=2
- @QaseSuite=Authentication\tLogin\tEdge Cases
+### Nested Suites
+
+Use tab character (`\t`) to separate suite levels:
+
+```gherkin
+Feature: User Authentication
+
+ @QaseID=1
+ @QaseSuite=Application\tAuthentication\tLogin\tEdge Cases
Scenario: Login with special characters
Given I am on the login page
When I enter credentials with special characters
Then I should be logged in
```
----
+> **Note:** Feature and Scenario Outline structure can also serve as natural suite hierarchy in Qase.
-## Ignoring a Test in Qase
+---
-To exclude a test from being reported to Qase (while still executing the test in Cucumber.js), use the `@QaseIgnore`
-tag. The test will run, but its result will not be sent to Qase.
+## Ignoring Tests
-### Example
+Exclude a scenario from Qase reporting using the `@QaseIgnore` tag. The scenario still executes, but results are not sent to Qase:
```gherkin
Feature: User Authentication
@QaseIgnore
- Scenario: This test will not be reported to Qase
+ Scenario: Test not reported to Qase
Given I am on the login page
When I enter valid credentials
Then I should be logged in
@@ -125,124 +160,714 @@ Feature: User Authentication
---
-## Adding Parameters to a Test
-
-You can add parameters to a test case using the `@QaseParameters` tag. This tag accepts a JSON object with
-parameter names and values.
+## Muting Tests
-### Example
+Mark a scenario as muted using the `@QaseMuted` tag. Muted tests are reported but do not affect the test run status:
```gherkin
Feature: User Authentication
- @QaseId=1
- @QaseParameters={"browser":"chrome","environment":"staging"}
- Scenario: Successful login
+ @QaseMuted
+ Scenario: Known failing test
Given I am on the login page
- When I enter valid credentials
- Then I should be logged in
+ When I enter invalid credentials
+ Then I should see an error
```
---
-## Adding Group Parameters to a Test
+## Working with Attachments
-To add group parameters to a test case, use the `@QaseGroupParameters` tag. This tag accepts a JSON object with
-group parameter names and values.
+Attach files, screenshots, logs, and other content to your test results using the `this.attach()` method in step definitions.
-### Example
+### Attach Screenshot in Step
+
+```javascript
+// step_definitions/login_steps.js
+const { Given, When, Then } = require('@cucumber/cucumber');
+
+Given('I am on the login page', async function() {
+ await this.page.goto('https://example.com/login');
+
+ // Attach screenshot
+ const screenshot = await this.page.screenshot();
+ await this.attach(screenshot, 'image/png');
+});
+```
+
+### Attach Text Content
+
+```javascript
+When('I enter valid credentials', async function() {
+ await this.page.fill('#username', 'testuser');
+ await this.page.fill('#password', 'password');
+
+ // Attach text log
+ await this.attach('Credentials entered successfully', 'text/plain');
+
+ await this.page.click('#login-button');
+});
+```
+
+### Attach JSON Data
+
+```javascript
+Then('I should be logged in', async function() {
+ await this.page.waitForSelector('.dashboard');
+
+ // Attach JSON data
+ const userData = { username: 'testuser', status: 'logged_in' };
+ await this.attach(JSON.stringify(userData, null, 2), 'application/json');
+});
+```
+
+### Attach in Hooks
+
+```javascript
+// support/hooks.js
+const { Before, After } = require('@cucumber/cucumber');
+
+After(async function(scenario) {
+ if (scenario.result.status === 'failed') {
+ const screenshot = await this.page.screenshot();
+ await this.attach(screenshot, 'image/png');
+ }
+});
+```
+
+### Supported MIME Types
+
+Common MIME types are auto-detected. You can also specify explicitly:
+
+| Extension | MIME Type |
+|-----------|-----------|
+| `.png` | `image/png` |
+| `.jpg`, `.jpeg` | `image/jpeg` |
+| `.gif` | `image/gif` |
+| `.txt` | `text/plain` |
+| `.json` | `application/json` |
+| `.xml` | `application/xml` |
+| `.html` | `text/html` |
+| `.pdf` | `application/pdf` |
+
+> For more details, see [Attachments Guide](ATTACHMENTS.md).
+
+---
+
+## Working with Steps
+
+CucumberJS automatically creates steps from your Gherkin scenarios. Each `Given`, `When`, `Then`, and `And` statement becomes a step in Qase.
+
+### Native Gherkin Step Mapping
```gherkin
Feature: User Authentication
- @QaseId=1
- @QaseParameters={"browser":"chrome","environment":"staging"}
- @QaseGroupParameters={"test_group":"authentication","test_type":"smoke"}
- Scenario: Successful login
+ @QaseID=1
+ Scenario: Login with detailed steps
Given I am on the login page
- When I enter valid credentials
- Then I should be logged in
+ When I enter username "testuser"
+ And I enter password "password123"
+ And I click the login button
+ Then I should see the dashboard
+ And I should see welcome message
```
+Each line above becomes a separate step in Qase with its own result.
+
+### Step Definitions
+
+```javascript
+// step_definitions/login_steps.js
+const { Given, When, Then } = require('@cucumber/cucumber');
+
+Given('I am on the login page', async function() {
+ await this.page.goto('https://example.com/login');
+});
+
+When('I enter username {string}', async function(username) {
+ await this.page.fill('#username', username);
+});
+
+When('I enter password {string}', async function(password) {
+ await this.page.fill('#password', password);
+});
+
+When('I click the login button', async function() {
+ await this.page.click('#login-button');
+});
+
+Then('I should see the dashboard', async function() {
+ await this.page.waitForSelector('.dashboard');
+});
+
+Then('I should see welcome message', async function() {
+ const message = await this.page.locator('.welcome').textContent();
+ expect(message).toContain('Welcome');
+});
+```
+
+> For more details, see [Steps Guide](STEPS.md).
+
---
-## Adding Steps to a Test
+## Working with Parameters
-Cucumber.js automatically creates steps from your Gherkin scenarios. Each `Given`, `When`, and `Then` statement
-becomes a step in Qase. You can also add custom step information in your step definitions.
+Report parameterized test data to Qase using tags or Scenario Outline.
-### Example
+### Using @QaseParameters Tag
```gherkin
-Feature: User Authentication
+Feature: Browser Compatibility
+
+ @QaseID=1
+ @QaseParameters={"browser":"Chrome","version":"110"}
+ Scenario: Test on Chrome
+ Given I open the application in Chrome
+ When I perform test actions
+ Then I should see expected results
+```
+
+### Using Scenario Outline (Native Parameterization)
+
+```gherkin
+Feature: Login with Different Credentials
+
+ @QaseID=1
+ Scenario Outline: Login with
+ Given I am on the login page
+ When I enter username ""
+ And I enter password ""
+ Then I should see
+
+ Examples:
+ | username | password | result |
+ | user1 | pass1 | dashboard |
+ | user2 | pass2 | dashboard |
+ | invalid | invalid | error message |
+```
+
+### Group Parameters
+
+```gherkin
+Feature: Environment Testing
+
+ @QaseID=1
+ @QaseParameters={"browser":"Chrome","os":"Windows"}
+ @QaseGroupParameters={"environment":"staging","region":"us-east"}
+ Scenario: Test in specific environment
+ Given I am testing in staging environment
+ When I run the test
+ Then results should be recorded
+```
+
+---
+
+## Multi-Project Support
+
+Send test results to multiple Qase projects simultaneously using special tag syntax.
+
+For detailed configuration, examples, and troubleshooting, see the [Multi-Project Support Guide](MULTI_PROJECT.md).
+
+---
+
+## Running Tests
+
+### Basic Execution
+
+```sh
+QASE_MODE=testops npx cucumber-js -f cucumberjs-qase-reporter
+```
+
+### With Specific Features
+
+```sh
+npx cucumber-js -f cucumberjs-qase-reporter features/login.feature
+```
- @QaseId=1
+### With Tag Filtering
+
+```sh
+npx cucumber-js -f cucumberjs-qase-reporter --tags "@smoke"
+```
+
+### With Tag Expression
+
+```sh
+npx cucumber-js -f cucumberjs-qase-reporter --tags "@QaseID and not @skip"
+```
+
+### Using Profile
+
+```javascript
+// cucumber.js
+module.exports = {
+ default: {
+ format: ['progress', 'cucumberjs-qase-reporter'],
+ require: ['step_definitions/**/*.js'],
+ publishQuiet: true,
+ },
+};
+```
+
+```sh
+npx cucumber-js --profile default
+```
+
+---
+
+## Complete Examples
+
+### Full Feature Example
+
+```gherkin
+Feature: User Authentication
+ As a user
+ I want to log in to the application
+ So I can access my account
+
+ Background:
+ Given the application is running
+ And the database is seeded
+
+ @QaseID=1
+ @QaseTitle=User can successfully log in with valid credentials
+ @QaseFields={"severity":"blocker","priority":"high","layer":"e2e","description":"Verifies complete login flow"}
+ @QaseSuite=Authentication\tLogin\tHappy Path
Scenario: Successful login
Given I am on the login page
- When I enter valid credentials
- Then I should be logged in
+ When I enter username "testuser@example.com"
+ And I enter password "SecurePass123!"
+ And I click the login button
+ Then I should be redirected to the dashboard
+ And I should see "Welcome back, Test User"
+ And I should see the logout button
+
+ @QaseID=2
+ @QaseFields={"severity":"critical","priority":"high","layer":"e2e"}
+ @QaseSuite=Authentication\tLogin\tEdge Cases
+ Scenario: Login with invalid credentials
+ Given I am on the login page
+ When I enter username "invalid@example.com"
+ And I enter password "wrongpassword"
+ And I click the login button
+ Then I should see an error message
+ And I should remain on the login page
```
+### Example Project Structure
+
+```
+my-project/
+├── qase.config.json
+├── cucumber.js
+├── features/
+│ ├── authentication.feature
+│ ├── checkout.feature
+│ └── ...
+├── step_definitions/
+│ ├── login_steps.js
+│ ├── checkout_steps.js
+│ └── ...
+├── support/
+│ ├── hooks.js
+│ └── world.js
+└── package.json
+```
+
+---
+
+## Troubleshooting
+
+### Tests Not Appearing in Qase
+
+1. Verify `mode` is set to `TestOps` (not `off` or `report`)
+2. Check API token has write permissions
+3. Verify project code is correct
+4. Check for errors in console output (enable `debug: true`)
+5. Ensure formatter is specified: `-f cucumberjs-qase-reporter`
+
+### Tag Parsing Errors
+
+**Problem:** `@QaseID` or `@QaseFields` tags not recognized
+
+**Solution:**
+1. Verify tag syntax: `@QaseID=1` (no spaces around `=`)
+2. For fields, use valid JSON: `@QaseFields={"severity":"high"}`
+3. Check for typos in tag names (case-sensitive)
+4. Ensure tags are at scenario level, not step level
+
+### Step Definition Not Found
+
+**Problem:** `Undefined. Implement with the following snippet:`
+
+**Solution:**
+1. Verify step definitions are in correct directory
+2. Check `require` path in cucumber.js configuration
+3. Ensure step patterns match exactly (including quotes and parameters)
+4. For TypeScript, verify ts-node is configured
+
+### World Object Issues
+
+**Problem:** `this.page is undefined` or similar errors
+
+**Solution:**
+1. Ensure World object is properly configured in `support/world.js`
+2. Use function expressions (not arrow functions) in step definitions
+3. Verify browser/page initialization happens in Before hook
+
```javascript
-// step_definitions/login_steps.js
-const { Given, When, Then } = require("@cucumber/cucumber");
+// support/world.js
+const { setWorldConstructor } = require('@cucumber/cucumber');
+
+class CustomWorld {
+ constructor() {
+ this.page = null;
+ this.browser = null;
+ }
+}
+
+setWorldConstructor(CustomWorld);
+```
+
+### Parallel Feature Execution
+
+**Problem:** Tests fail or report incorrectly when running in parallel
+
+**Solution:**
+1. CucumberJS parallel execution requires careful World isolation
+2. Ensure each scenario has independent setup/teardown
+3. Avoid shared state between scenarios
+4. Consider using `--parallel` flag limitations
+
+```sh
+npx cucumber-js -f cucumberjs-qase-reporter --parallel 2
+```
+
+### Attachments Not Uploading
+
+**Problem:** Screenshots or logs not appearing in Qase
+
+**Solution:**
+1. Verify `this.attach()` is called with correct MIME type
+2. Check file/buffer content is valid
+3. Enable debug logging to see attachment processing
+4. Ensure attachments are called within step/hook context
+
+---
+
+## Integration Patterns
+
+### Organizing Step Definitions
+
+**By domain:**
+```
+step_definitions/
+├── authentication_steps.js
+├── user_management_steps.js
+├── checkout_steps.js
+└── common_steps.js
+```
+
+**Each file focuses on one domain:**
+```javascript
+// step_definitions/authentication_steps.js
+const { Given, When, Then } = require('@cucumber/cucumber');
+
+Given('I am on the login page', async function() {
+ await this.page.goto('/login');
+});
+
+When('I log in as {string}', async function(role) {
+ const credentials = this.testData.users[role];
+ await this.page.fill('#username', credentials.username);
+ await this.page.fill('#password', credentials.password);
+ await this.page.click('#login-button');
+});
+```
+
+### Using World Objects for State
+
+```javascript
+// support/world.js
+const { setWorldConstructor, Before, After } = require('@cucumber/cucumber');
+const { chromium } = require('playwright');
+
+class CustomWorld {
+ constructor() {
+ this.browser = null;
+ this.page = null;
+ this.testData = {};
+ }
+
+ async init() {
+ this.browser = await chromium.launch();
+ this.page = await this.browser.newPage();
+ }
+
+ async cleanup() {
+ if (this.page) await this.page.close();
+ if (this.browser) await this.browser.close();
+ }
+}
+
+setWorldConstructor(CustomWorld);
+
+Before(async function() {
+ await this.init();
+});
-Given("I am on the login page", async function() {
- // Step implementation
- await this.page.goto("https://example.com/login");
+After(async function() {
+ await this.cleanup();
});
+```
+
+### Before/After Hooks with Qase
-When("I enter valid credentials", async function() {
- // Step implementation
- await this.page.fill("#username", "testuser");
- await this.page.fill("#password", "password");
- await this.page.click("#login-button");
+```javascript
+// support/hooks.js
+const { Before, After, Status } = require('@cucumber/cucumber');
+
+Before({ tags: '@require-auth' }, async function() {
+ // Login before scenarios tagged with @require-auth
+ await this.page.goto('/login');
+ await this.page.fill('#username', 'testuser');
+ await this.page.fill('#password', 'password');
+ await this.page.click('#login-button');
});
-Then("I should be logged in", async function() {
- // Step implementation
- await this.page.waitForSelector(".dashboard");
+After(async function(scenario) {
+ // Attach screenshot on failure
+ if (scenario.result.status === Status.FAILED) {
+ const screenshot = await this.page.screenshot({ fullPage: true });
+ await this.attach(screenshot, 'image/png');
+
+ // Attach page HTML
+ const html = await this.page.content();
+ await this.attach(html, 'text/html');
+
+ // Attach console logs
+ const logs = this.consoleMessages || [];
+ await this.attach(JSON.stringify(logs, null, 2), 'application/json');
+ }
});
```
+### Scenario Outline Patterns
+
+```gherkin
+Feature: Data-Driven Testing
+
+ @QaseID=1
+ Scenario Outline: Validate - in cart
+ Given I have "
- " in my cart
+ When I proceed to checkout
+ Then the total should be
+
+ @smoke
+ Examples: Common items
+ | item | quantity | expected_total |
+ | Apple | 2 | $4.00 |
+ | Banana | 3 | $3.00 |
+
+ @regression
+ Examples: Edge cases
+ | item | quantity | expected_total |
+ | Luxury Item | 1 | $1000.00 |
+ | Bulk Item | 100 | $500.00 |
+```
+
+### Tag Expressions for Filtering
+
+```sh
+# Run only smoke tests
+npx cucumber-js --tags "@smoke"
+
+# Run smoke tests that are not skipped
+npx cucumber-js --tags "@smoke and not @skip"
+
+# Run tests with QaseID or marked as critical
+npx cucumber-js --tags "@QaseID or @critical"
+
+# Complex expression
+npx cucumber-js --tags "(@smoke or @regression) and not (@wip or @skip)"
+```
+
---
-## Attaching Files to a Test
+## Common Use Cases
-You can attach files to test results using the `this.attach()` method in your step definitions. This method supports
-attaching files with content, paths, or media types.
+### Use Case 1: Tag Scenarios for Specific Qase Projects
-### Example
+```gherkin
+Feature: Multi-Project Reporting
+
+ @QaseID.PROJ1=1
+ @QaseID.PROJ2=5
+ Scenario: Test reported to multiple projects
+ Given I am testing shared functionality
+ When I execute the test
+ Then results are sent to both projects
+```
+
+### Use Case 2: Attach Screenshots in After Hook
```javascript
-// step_definitions/login_steps.js
-const { Given, When, Then } = require("@cucumber/cucumber");
+// support/hooks.js
+const { After, Status } = require('@cucumber/cucumber');
-Given("I am on the login page", async function() {
- await this.page.goto("https://example.com/login");
-
- // Attach screenshot
+After(async function(scenario) {
+ // Always attach screenshot at end
const screenshot = await this.page.screenshot();
- await this.attach(screenshot, "image/png");
+ await this.attach(screenshot, 'image/png');
+
+ // Attach failure details if failed
+ if (scenario.result.status === Status.FAILED) {
+ const html = await this.page.content();
+ await this.attach(html, 'text/html');
+
+ const errorLog = `
+Scenario: ${scenario.pickle.name}
+Status: ${scenario.result.status}
+Error: ${scenario.result.message}
+ `.trim();
+
+ await this.attach(errorLog, 'text/plain');
+ }
});
+```
+
+### Use Case 3: Use Scenario Outline for Parameterized Testing
+
+```gherkin
+Feature: Login with Different Browsers
+
+ @QaseID=1
+ Scenario Outline: Login on
+ Given I am using browser
+ When I navigate to the login page
+ And I enter valid credentials
+ And I click login button
+ Then I should be logged in successfully
+
+ Examples:
+ | browser |
+ | Chrome |
+ | Firefox |
+ | Safari |
+```
+
+### Use Case 4: Filter by @QaseID Tags
+
+```sh
+# Run only tests with QaseID
+npx cucumber-js --tags "@QaseID"
+
+# Run specific QaseID
+npx cucumber-js features/login.feature:12 # Line number
+
+# Run all except ignored tests
+npx cucumber-js --tags "not @QaseIgnore"
+```
+
+### Use Case 5: Background Steps for Common Setup
+
+```gherkin
+Feature: Shopping Cart
+
+ Background:
+ Given the application is running
+ And I am logged in as "standard_user"
+ And my cart is empty
+
+ @QaseID=1
+ Scenario: Add item to cart
+ When I add "Laptop" to cart
+ Then cart should contain 1 item
+
+ @QaseID=2
+ Scenario: Remove item from cart
+ Given I have "Laptop" in my cart
+ When I remove "Laptop" from cart
+ Then cart should be empty
+```
+
+Background steps run before each scenario and are reported as part of the test flow.
+
+### Use Case 6: Complex Test with Rich Metadata
+
+```gherkin
+Feature: E2E Checkout Flow
+
+ @QaseID=1
+ @QaseTitle=User can complete full checkout process from cart to confirmation
+ @QaseFields={"severity":"blocker","priority":"high","layer":"e2e","description":"Tests complete checkout including payment processing","preconditions":"- User account exists\n- Payment method configured\n- Product catalog seeded","postconditions":"Order created in database and confirmation email sent"}
+ @QaseSuite=E2E\tCheckout\tHappy Path
+ @QaseParameters={"payment_method":"credit_card","shipping":"standard"}
+ Scenario: Complete checkout with credit card
+ Given I am logged in
+ And I have items in my cart
+ When I proceed to checkout
+ And I enter shipping address
+ And I select "standard" shipping
+ And I enter credit card details
+ And I review my order
+ And I confirm the purchase
+ Then I should see order confirmation
+ And I should receive confirmation email
+ And order should exist in database
+```
+
+### Use Case 7: API Testing with CucumberJS
-When("I enter valid credentials", async function() {
- await this.page.fill("#username", "testuser");
- await this.page.fill("#password", "password");
-
- // Attach text content
- await this.attach("Credentials entered successfully", "text/plain");
-
- await this.page.click("#login-button");
+```gherkin
+Feature: API User Management
+
+ @QaseID=10
+ @QaseFields={"layer":"api","severity":"high"}
+ Scenario: Create user via API
+ Given the API is available
+ When I send POST request to "/users" with:
+ | field | value |
+ | name | John Doe |
+ | email | john@example.com |
+ | role | admin |
+ Then the response status should be 201
+ And the response should contain user ID
+ And the user should exist in database
+```
+
+```javascript
+// step_definitions/api_steps.js
+const { Given, When, Then } = require('@cucumber/cucumber');
+const axios = require('axios');
+
+When('I send POST request to {string} with:', async function(endpoint, dataTable) {
+ const data = {};
+ dataTable.hashes().forEach(row => {
+ data[row.field] = row.value;
+ });
+
+ this.response = await axios.post(`https://api.example.com${endpoint}`, data);
+
+ // Attach request/response
+ await this.attach(JSON.stringify({
+ request: { endpoint, data },
+ response: this.response.data,
+ }, null, 2), 'application/json');
});
-Then("I should be logged in", async function() {
- await this.page.waitForSelector(".dashboard");
-
- // Attach JSON data
- const userData = { username: "testuser", status: "logged_in" };
- await this.attach(JSON.stringify(userData, null, 2), "application/json");
+Then('the response status should be {int}', function(expectedStatus) {
+ expect(this.response.status).toBe(expectedStatus);
});
```
---
+
+## See Also
+
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Attachments Guide](ATTACHMENTS.md)
+- [Steps Guide](STEPS.md)
+- [Multi-Project Support](MULTI_PROJECT.md)
+- [Upgrade Guide](UPGRADE.md)
diff --git a/qase-cypress/README.md b/qase-cypress/README.md
index cff6e8e4..a569ef23 100644
--- a/qase-cypress/README.md
+++ b/qase-cypress/README.md
@@ -1,292 +1,297 @@
-# Qase TMS Cypress reporter
+# [Qase TestOps](https://qase.io) Cypress Reporter
-Publish results simple and easy.
+[](https://www.apache.org/licenses/LICENSE-2.0)
+[](https://www.npmjs.com/package/cypress-qase-reporter)
-## Installation
+Qase Cypress Reporter enables seamless integration between your Cypress tests and [Qase TestOps](https://qase.io), providing automatic test result reporting, test case management, and comprehensive test analytics.
-To install the latest release version, run:
+## Features
-```sh
-npm install -D cypress-qase-reporter
-```
+- Link automated tests to Qase test cases by ID
+- Auto-create test cases from your test code
+- Report test results with rich metadata (fields, attachments, steps)
+- Support for parameterized tests
+- Multi-project reporting support
+- Flexible configuration (file, environment variables, Cypress config)
+- Cucumber/Gherkin integration support
+- Automatic screenshot and video attachments
-## Updating from v2.3.x to v3.0.x
-
-To update an existing test project using Qase reporter from version 2.3.x to version 3.0.x,
-run the following steps:
-
-- Update reporter configuration in `cypress.config.js` file.
-
- ```diff
- + import { afterSpecHook } from 'cypress-qase-reporter/hooks';
- ...
- reporter: 'cypress-multi-reporters',
- reporterOptions: {
- reporterEnabled: 'cypress-qase-reporter',
- cypressQaseReporterReporterOptions: {
- ... // other options
- framework: {
- cypress: {
- screenshotsFolder: 'cypress/screenshots',
- + videosFolder: 'cypress/videos',
- + uploadDelay: 10, // Delay in seconds before uploading video files (default: 10)
- },
- },
- },
- },
- video: true,
- e2e: {
- setupNodeEvents(on, config) {
- require('cypress-qase-reporter/plugin')(on, config)
- require('cypress-qase-reporter/metadata')(on)
- + on('after:spec', async (spec, results) => {
- + await afterSpecHook(spec, config);
- + });
- }
- }
- ...
- ```
-
-## Updating from v2.1 to v2.2
-
-To update an existing test project using Qase reporter from version 2.1 to version 2.2,
-run the following steps:
-
-- Add metadata in the `e2e` section of `cypress.config.js`
-
- ```diff
- ...
- e2e: {
- setupNodeEvents(on, config) {
- require('cypress-qase-reporter/plugin')(on, config)
- + require('cypress-qase-reporter/metadata')(on)
- }
- }
- ...
- ```
-
-## Updating from v1 to v2.1
-
-To update an existing test project using Qase reporter from version 1 to version 2.1,
-run the following steps:
-
-1. Change import paths in your test files:
-
- ```diff
- - import { qase } from 'cypress-qase-reporter/dist/mocha'
- + import { qase } from 'cypress-qase-reporter/mocha'
- ```
-
-2. Update reporter configuration in `cypress.config.js` and/or environment variables —
- see the [configuration reference](#configuration) below.
-
-3. Add a hook in the `e2e` section of `cypress.config.js`:
-
- ```diff
- ...
- e2e: {
- + setupNodeEvents(on, config) {
- + require('cypress-qase-reporter/plugin')(on, config)
- }
- }
- ...
- ```
-
- If you already override `before:run` or `after:run` hooks in your `cypress.config.js`, use this instead:
-
- ```diff
- const { beforeRunHook, afterRunHook } = require('cypress-qase-reporter/hooks');
-
- ...
- e2e: {
- setupNodeEvents(on, config) {
- + on('before:run', async () => {
- + console.log('override before:run');
- + await beforeRunHook(config);
- + });
-
- + on('after:run', async () => {
- + console.log('override after:run');
- + await afterRunHook(config);
- + });
- },
- },
- ...
- ```
-
-## Getting started
-
-The Cypress reporter can auto-generate test cases
-and suites from your test data.
-Test results of subsequent test runs will match the same test cases
-as long as their names and file paths don't change.
-
-You can also annotate the tests with the IDs of existing test cases
-from Qase.io before executing tests. It's a more reliable way to bind
-autotests to test cases, that persists when you rename, move, or
-parameterize your tests.
-
-### Metadata
-
-- `qase.title` - set the title of the test case
-- `qase.fields` - set the fields of the test case
-- `qase.suite` - set the suite of the test case
-- `qase.comment` - set the comment of the test case
-- `qase.parameters` - set the parameters of the test case
-- `qase.groupParameters` - set the group parameters of the test case
-- `qase.ignore` - ignore the test case in Qase. The test will be executed, but the results will not be sent to Qase.
-- `qase.step` - create a step in the test case
-- `qase.attach` - attach a file or content to the test case
-
-#### Cucumber-specific
-
-- `addCucumberStep(stepName)` - manually add a Cucumber step to Qase report (useful for `@badeball/cypress-cucumber-preprocessor`)
-
-For detailed instructions on using annotations and methods, refer to [Usage](docs/usage.md).
-
-For example:
-
-```typescript
-import { qase } from 'cypress-qase-reporter/mocha';
+## Installation
-describe('My First Test', () => {
- qase(1,
- it('Several ids', () => {
- qase.title('My title');
- expect(true).to.equal(true);
- })
- );
- // a test can check multiple test cases
- qase([2, 3],
- it('Correct test', () => {
- expect(true).to.equal(true);
- })
- );
- qase(4,
- it.skip('Skipped test', () => {
- expect(true).to.equal(true);
- })
- );
-});
+```sh
+npm install --save-dev cypress-qase-reporter
```
-To execute Cypress tests and report them to Qase.io, run the command:
+## Quick Start
-```bash
-QASE_MODE=testops npx cypress run
+**1. Create `qase.config.json` in your project root:**
+
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "project": "YOUR_PROJECT_CODE",
+ "api": {
+ "token": "YOUR_API_TOKEN"
+ }
+ }
+}
```
-or
+**2. Configure Cypress to use the reporter in `cypress.config.js`:**
-```bash
-npm test
-```
+```javascript
+const { defineConfig } = require('cypress');
-You can try it with the example project at [`examples/cypress`](../examples/cypress/).
+module.exports = defineConfig({
+ reporter: 'cypress-qase-reporter',
+ reporterOptions: {
+ mode: 'testops',
+ },
+ e2e: {
+ setupNodeEvents(on, config) {
+ require('cypress-qase-reporter/plugin')(on, config);
+ require('cypress-qase-reporter/metadata')(on);
+ },
+ },
+});
+```
-
-
-
+**3. Add Qase ID to your test:**
-A test run will be performed and available at:
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+describe('Login Suite', () => {
+ it(qase(1, 'User can login with valid credentials'), () => {
+ cy.visit('https://example.com/login');
+ cy.get('#email').type('user@example.com');
+ cy.get('#password').type('password123');
+ cy.get('button[type="submit"]').click();
+ cy.url().should('include', '/dashboard');
+ });
+});
```
-https://app.qase.io/run/QASE_PROJECT_CODE
+
+**4. Run your tests:**
+
+```sh
+npx cypress run
```
## Configuration
-Qase Cypress reporter can be configured in multiple ways:
+The reporter is configured via (in order of priority):
+
+1. **cypress.config.js** (Cypress-specific, highest priority)
+2. **Environment variables** (`QASE_*`)
+3. **Config file** (`qase.config.json`)
-- by adding configuration block in `cypress.config.js`,
-- using a separate config file `qase.config.json`,
-- using environment variables (they override the values from the configuration files).
+### Minimal Configuration
-For a full list of configuration options, see
-the [Configuration reference](../qase-javascript-commons/README.md#configuration).
+| Option | Environment Variable | Description |
+|--------|---------------------|-------------|
+| `mode` | `QASE_MODE` | Set to `testops` to enable reporting |
+| `testops.project` | `QASE_TESTOPS_PROJECT` | Your Qase project code |
+| `testops.api.token` | `QASE_TESTOPS_API_TOKEN` | Your Qase API token |
-Example `cypress.config.js` config:
+### Example `cypress.config.js`
```javascript
-import cypress from 'cypress';
-import { afterSpecHook } from 'cypress-qase-reporter/hooks';
+const { defineConfig } = require('cypress');
-module.exports = cypress.defineConfig({
- reporter: 'cypress-multi-reporters',
+module.exports = defineConfig({
+ reporter: 'cypress-qase-reporter',
reporterOptions: {
- reporterEnabled: 'cypress-mochawesome-reporter, cypress-qase-reporter',
- cypressMochawesomeReporterReporterOptions: {
- charts: true,
+ mode: 'testops',
+ debug: false,
+ testops: {
+ api: {
+ token: process.env.QASE_API_TOKEN,
+ },
+ project: 'DEMO',
+ uploadAttachments: true,
+ run: {
+ title: 'Cypress Automated Run',
+ complete: true,
+ },
+ batch: {
+ size: 100,
+ },
},
- cypressQaseReporterReporterOptions: {
- debug: true,
-
- testops: {
- api: {
- token: 'api_key',
- },
-
- project: 'project_code',
- uploadAttachments: true,
-
- run: {
- complete: true,
- },
+ framework: {
+ cypress: {
+ screenshotsFolder: 'cypress/screenshots',
+ videosFolder: 'cypress/videos',
},
-
- framework: {
- cypress: {
- screenshotsFolder: 'cypress/screenshots',
- videosFolder: 'cypress/videos',
- uploadDelay: 10, // Delay in seconds before uploading video files (default: 10)
- }
- }
},
},
- video: false,
e2e: {
setupNodeEvents(on, config) {
- require('cypress-qase-reporter/plugin')(on, config)
- require('cypress-qase-reporter/metadata')(on)
- on('after:spec', async (spec, results) => {
- await afterSpecHook(spec, config);
- });
+ require('cypress-qase-reporter/plugin')(on, config);
+ require('cypress-qase-reporter/metadata')(on);
},
},
});
```
-Check out the example of configuration for multiple reporters in the
-[demo project](../examples/cypress/cypress.config.js).
+### Example `qase.config.json`
-### Multi-Project Support
+```json
+{
+ "mode": "testops",
+ "fallback": "report",
+ "testops": {
+ "project": "YOUR_PROJECT_CODE",
+ "api": {
+ "token": "YOUR_API_TOKEN"
+ },
+ "run": {
+ "title": "Cypress Automated Run"
+ },
+ "batch": {
+ "size": 100
+ }
+ },
+ "report": {
+ "driver": "local",
+ "connection": {
+ "local": {
+ "path": "./build/qase-report",
+ "format": "json"
+ }
+ }
+ },
+ "framework": {
+ "cypress": {
+ "screenshotsFolder": "cypress/screenshots",
+ "videosFolder": "cypress/videos"
+ }
+ }
+}
+```
+
+> **Full configuration reference:** See [qase-javascript-commons](../qase-javascript-commons/README.md) for all available options including logging, status mapping, execution plans, and more.
+
+## Usage
+
+### Link Tests with Test Cases
+
+Associate your tests with Qase test cases using test case IDs:
+
+**Single ID:**
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+describe('Authentication', () => {
+ it(qase(1, 'Login with valid credentials'), () => {
+ cy.visit('/login');
+ cy.get('#email').type('user@example.com');
+ cy.get('button').click();
+ });
+});
+```
+
+**Multiple IDs:**
+
+```javascript
+describe('Authentication', () => {
+ it(qase([1, 2, 3], 'Login works across multiple browsers'), () => {
+ cy.visit('/login');
+ cy.get('#email').type('user@example.com');
+ cy.get('button').click();
+ });
+});
+```
-Qase Cypress Reporter supports sending test results to multiple Qase projects simultaneously. You can specify different test case IDs for each project using `qase.projects(mapping, it(...))`.
+### Add Metadata
-For detailed information, configuration, and examples, see the [Multi-Project Support Guide](docs/MULTI_PROJECT.md).
+Enhance your tests with additional information:
-## Cucumber/Gherkin Integration
+```javascript
+it('User can login', () => {
+ qase.title('User successfully logs in with valid credentials');
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'e2e',
+ description: 'Tests the core login flow',
+ });
+ qase.suite('Authentication / Login');
+
+ cy.visit('/login');
+ cy.get('#email').type('user@example.com');
+ cy.get('#password').type('password123');
+ cy.get('button').click();
+});
+```
+
+### Ignore Tests
+
+Exclude specific tests from Qase reporting (test still runs, but results are not sent):
-If you use Cucumber with Gherkin feature files in your Cypress tests, Qase reporter provides full support for both the legacy `cypress-cucumber-preprocessor` and the modern `@badeball/cypress-cucumber-preprocessor`.
+```javascript
+it('Test under development', () => {
+ qase.ignore();
+ cy.visit('/new-feature');
+ cy.get('.coming-soon').should('be.visible');
+});
+```
-The reporter can automatically:
+### Test Result Statuses
-- Extract test cases from feature files
-- Report individual Gherkin steps (Given/When/Then) with their execution status
-- Link test results to Qase test cases using `@QaseID` tags
-- Attach screenshots and videos to test results
+| Cypress Result | Qase Status |
+|----------------|-------------|
+| Passed | Passed |
+| Failed | Failed |
+| Pending | Skipped |
+| Skipped | Skipped |
-**📚 For detailed instructions, configuration examples, and troubleshooting, see the [Cucumber Integration Guide](docs/cucumber.md).**
+> For more usage examples, see the [Usage Guide](docs/usage.md).
-**Quick links:**
+## Running Tests
-- [Example project for @badeball/cypress-cucumber-preprocessor](../examples/cypressBadeballCucumber/)
-- [Example project for cypress-cucumber-preprocessor (legacy)](../examples/cypressCucumber/)
+Run Cypress tests with Qase reporting:
+
+```bash
+# Run all tests
+npx cypress run
+
+# Run specific spec file
+npx cypress run --spec "cypress/e2e/login.cy.js"
+
+# Run in headed mode
+npx cypress open
+
+# Run with environment variables
+QASE_MODE=testops QASE_TESTOPS_PROJECT=DEMO npx cypress run
+```
## Requirements
-We maintain the reporter on [LTS versions of Node](https://nodejs.org/en/about/releases/).
+- Node.js >= 14
+- Cypress >= 10.0.0
+
+## Documentation
+
+| Guide | Description |
+|-------|-------------|
+| [Usage Guide](docs/usage.md) | Complete usage reference with all methods and options |
+| [Cucumber Integration](docs/cucumber.md) | Gherkin/Cucumber support with preprocessors |
+| [Attachments](docs/ATTACHMENTS.md) | Adding screenshots, logs, and files to test results |
+| [Steps](docs/STEPS.md) | Defining test steps for detailed reporting |
+| [Multi-Project Support](docs/MULTI_PROJECT.md) | Reporting to multiple Qase projects |
+| [Upgrade Guide](docs/UPGRADE.md) | Migration guide for breaking changes |
+
+## Examples
+
+See the [examples directory](../examples/) for complete working examples:
+
+- [Single project example](../examples/single/cypress/)
+- [Cucumber (badeball) example](../examples/single/cypressBadeballCucumber/)
+- [Cucumber (legacy) example](../examples/single/cypressCucumber/)
-`cypress >= 8.0.0`
+## License
-
+Apache License 2.0. See [LICENSE](LICENSE) for details.
diff --git a/qase-cypress/docs/ATTACHMENTS.md b/qase-cypress/docs/ATTACHMENTS.md
new file mode 100644
index 00000000..fa6aab12
--- /dev/null
+++ b/qase-cypress/docs/ATTACHMENTS.md
@@ -0,0 +1,308 @@
+# Attachments in Cypress
+
+This guide covers how to attach files, screenshots, logs, and other content to your Qase test results.
+
+---
+
+## Overview
+
+Qase Cypress Reporter supports attaching various types of content to test results:
+
+- **Files** — Attach files from the filesystem
+- **Screenshots** — Attach images captured during test execution
+- **Logs** — Attach text logs or console output
+- **Binary data** — Attach any binary content from memory
+
+Attachments can be added to:
+- **Test cases** — Visible in the overall test result
+- **Test steps** — Visible in specific step results
+
+---
+
+## Attaching Files
+
+### From File Path
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+it('Test with file attachment', () => {
+ qase.attach({ paths: './cypress/fixtures/test-file.txt' });
+
+ cy.visit('https://example.com');
+ cy.contains('Example').should('be.visible');
+});
+```
+
+### Multiple Files
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+it('Test with multiple attachments', () => {
+ qase.attach({
+ paths: [
+ './cypress/fixtures/file1.txt',
+ './cypress/fixtures/file2.log',
+ './cypress/screenshots/test.png'
+ ]
+ });
+
+ cy.visit('https://example.com');
+});
+```
+
+---
+
+## Attaching Content from Memory
+
+### Text Content
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+it('Test with text attachment', () => {
+ qase.attach({
+ name: 'log.txt',
+ content: 'Test execution log content',
+ contentType: 'text/plain',
+ });
+
+ cy.visit('https://example.com');
+});
+```
+
+### Binary Content (Screenshots)
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+it('Test with screenshot attachment', () => {
+ cy.visit('https://example.com');
+
+ cy.screenshot('test-screenshot', { capture: 'viewport' });
+
+ // Manually attach screenshot
+ cy.readFile('cypress/screenshots/test-screenshot.png', 'base64').then((content) => {
+ qase.attach({
+ name: 'screenshot.png',
+ content: Buffer.from(content, 'base64'),
+ contentType: 'image/png',
+ });
+ });
+});
+```
+
+### JSON Data
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+it('Test with JSON attachment', () => {
+ const data = {
+ userId: 123,
+ status: 'active',
+ timestamp: new Date().toISOString(),
+ };
+
+ qase.attach({
+ name: 'test-data.json',
+ content: JSON.stringify(data, null, 2),
+ contentType: 'application/json',
+ });
+
+ cy.visit('https://example.com');
+});
+```
+
+---
+
+## Attaching to Steps
+
+Attach content to a specific test step:
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+it('Test with step attachments', () => {
+ qase.step('Navigate to page', () => {
+ cy.visit('https://example.com');
+
+ qase.attach({
+ name: 'navigation-log.txt',
+ content: 'Navigated to example.com',
+ contentType: 'text/plain',
+ });
+ });
+
+ qase.step('Interact with page', () => {
+ cy.get('button#submit').click();
+
+ qase.attach({
+ name: 'interaction-log.txt',
+ content: 'Clicked submit button',
+ contentType: 'text/plain',
+ });
+ });
+});
+```
+
+---
+
+## Method Reference
+
+### `qase.attach()`
+
+Attach content to the test case.
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `paths` | `string` or `string[]` | No* | Path(s) to file(s) to attach |
+| `content` | `string` or `Buffer` | No* | Content to attach |
+| `name` | `string` | No | Custom filename (auto-detected from path) |
+| `contentType` | `string` | No | MIME type (auto-detected from extension) |
+
+\* Either `paths` or `content` must be provided, but not both.
+
+**CommonJS:**
+```javascript
+const { qase } = require('cypress-qase-reporter/mocha');
+
+qase.attach({ paths: './path/to/file.txt' });
+```
+
+**ES Modules:**
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+qase.attach({ paths: './path/to/file.txt' });
+```
+
+---
+
+## MIME Types
+
+Common MIME types are auto-detected based on file extension:
+
+| Extension | MIME Type |
+|-----------|-----------|
+| `.png` | `image/png` |
+| `.jpg`, `.jpeg` | `image/jpeg` |
+| `.gif` | `image/gif` |
+| `.svg` | `image/svg+xml` |
+| `.txt` | `text/plain` |
+| `.log` | `text/plain` |
+| `.json` | `application/json` |
+| `.xml` | `application/xml` |
+| `.html` | `text/html` |
+| `.csv` | `text/csv` |
+| `.pdf` | `application/pdf` |
+| `.zip` | `application/zip` |
+
+For other file types, specify `contentType` explicitly.
+
+---
+
+## Common Use Cases
+
+### Cypress Screenshots
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+it('Test with automatic screenshot capture', () => {
+ cy.visit('https://example.com');
+ cy.get('button#submit').click();
+
+ // Cypress captures screenshots automatically on failure
+ // For manual screenshots:
+ cy.screenshot('manual-screenshot');
+
+ cy.contains('Success').should('be.visible');
+});
+```
+
+**Note:** Cypress automatically captures screenshots on test failure. Use `cy.screenshot()` for manual screenshots during test execution.
+
+### API Response Logs
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+it('API request with response logging', () => {
+ cy.request('GET', 'https://api.example.com/users').then((response) => {
+ qase.attach({
+ name: 'api-response.json',
+ content: JSON.stringify(response.body, null, 2),
+ contentType: 'application/json',
+ });
+
+ expect(response.status).to.eq(200);
+ });
+});
+```
+
+### Browser Console Logs
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+it('Test with captured console logs', () => {
+ const logs = [];
+
+ cy.visit('https://example.com', {
+ onBeforeLoad(win) {
+ cy.stub(win.console, 'log').callsFake((message) => {
+ logs.push(message);
+ });
+ },
+ });
+
+ cy.window().then((win) => {
+ win.console.log('Test log 1');
+ win.console.log('Test log 2');
+
+ qase.attach({
+ name: 'console-logs.txt',
+ content: logs.join('\n'),
+ contentType: 'text/plain',
+ });
+ });
+});
+```
+
+---
+
+## Troubleshooting
+
+### Attachments Not Appearing
+
+1. Verify the file path exists and is readable
+2. Check file permissions
+3. Enable debug logging to see upload status:
+ ```json
+ {
+ "debug": true
+ }
+ ```
+
+### Large Files
+
+Large attachments may slow down test execution. Consider:
+- Compressing logs before attaching
+- Using selective logging (e.g., only on failure)
+- Setting reasonable size limits
+
+### Binary Data Issues
+
+When attaching binary data, always specify:
+- `name` with appropriate extension
+- `contentType` if extension doesn't match content type
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Steps Guide](STEPS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-cypress/docs/MULTI_PROJECT.md b/qase-cypress/docs/MULTI_PROJECT.md
index d6c49a77..038078e0 100644
--- a/qase-cypress/docs/MULTI_PROJECT.md
+++ b/qase-cypress/docs/MULTI_PROJECT.md
@@ -6,6 +6,8 @@ Qase Cypress Reporter supports sending test results to multiple Qase projects si
* Different projects track the same functionality with different test case IDs
* You want to maintain separate test runs for different environments or teams
+---
+
## Configuration
For detailed configuration options, refer to the [qase-javascript-commons README](../../qase-javascript-commons/README.md#multi-project-support).
@@ -14,29 +16,33 @@ For detailed configuration options, refer to the [qase-javascript-commons README
To enable multi-project support, set the mode to `testops_multi` in your Cypress reporter options (e.g. in `cypress.config.js` or `qase.config.json`):
+**Example configuration:**
+
```json
{
"mode": "testops_multi",
- "testops": {
- "api": { "token": "", "host": "qase.io" },
- "batch": { "size": 100 }
- },
"testops_multi": {
"default_project": "PROJ1",
"projects": [
{
"code": "PROJ1",
- "run": { "title": "PROJ1 Cypress Run", "complete": true }
+ "api": {
+ "token": "your_api_token_for_proj1"
+ }
},
{
"code": "PROJ2",
- "run": { "title": "PROJ2 Cypress Run", "complete": true }
+ "api": {
+ "token": "your_api_token_for_proj2"
+ }
}
]
}
}
```
+---
+
## Using `qase.projects()`
The `qase.projects(mapping, nameOrTest)` helper lets you map a test to one or more projects and case IDs.
@@ -46,7 +52,7 @@ The `qase.projects(mapping, nameOrTest)` helper lets you map a test to one or mo
Pass the result of `it()` as the second argument so the test title is updated with markers; the reporter will parse them and set `testops_project_mapping`:
```javascript
-const { qase } = require('cypress-qase-reporter');
+import { qase } from 'cypress-qase-reporter/mocha';
describe('Suite', () => {
qase.projects({ PROJ1: [1], PROJ2: [2] }, it('A test reported to two projects', () => {
@@ -70,38 +76,122 @@ You can also pass a title string; the helper returns the formatted title for use
```javascript
it(qase.projects({ PROJ1: [100], PROJ2: [200] }, 'Login flow'), () => {
cy.visit('/login');
+ cy.get('#username').type('testuser');
+ cy.get('#login-button').click();
});
```
+**Key points:**
+
+- Single project: `it(qase(100, 'test name'), () => { ... })`
+- Multi-project: `it(qase.projects({ PROJ1: [100], PROJ2: [200] }, 'test name'), () => { ... })`
+- Multiple IDs per project: `it(qase.projects({ PROJ1: [10, 11], PROJ2: [20] }, 'test name'), () => { ... })`
+
### Combining with other Qase methods
Use `qase.projects()` together with other Qase methods (e.g. `qase.title()`, `qase.attach()`) inside the test.
+Project codes (e.g. `PROJ1`, `PROJ2`) must match `testops_multi.projects[].code` in your config.
+
+---
+
## Tests Without Project Mapping
If a test does not use `qase.projects()` and has no `(Qase PROJ: ids)` markers in the title, it is sent to the `default_project` from your configuration. If the test also has no `(Qase ID: …)` legacy ID, the result is sent to the default project without linking to a test case.
+---
+
## Important Notes
1. **Project codes must match**: The project codes in `qase.projects({ PROJ1: [1], ... })` must exactly match the codes in `testops_multi.projects[].code`.
-
-2. **Mode requirement**: Set `mode` to `testops_multi` in your reporter config. Single-project mode (`testops`) does not use project mapping.
-
+2. **Mode requirement**: Set `mode` to `testops_multi` in your reporter config. Single-project mode (`TestOps`) does not use project mapping.
3. **Cucumber/BDD**: When using Cypress with Cucumber (e.g. `@badeball/cypress-cucumber-preprocessor`), use tags in feature files: `@qaseid.PROJ1(1) @qaseid.PROJ2(2)`. See [Cucumber documentation](cucumber.md).
+4. **API tokens**: Each project in `testops_multi.projects[]` can have its own API token for separate authentication.
+
+---
## Examples
See the [multi-project Cypress example](../../examples/multiProject/cypress/) for a complete runnable setup.
+### Complete Example
+
+Here's a complete Cypress test file showing multi-project usage:
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+describe('Multi-project test suite', () => {
+ // Test reported to two projects
+ it(qase.projects({ PROJ1: [1], PROJ2: [2] }, 'User can login successfully'), () => {
+ cy.visit('https://example.cypress.io');
+ cy.get('#username').type('testuser');
+ cy.get('#password').type('password123');
+ cy.get('#login-button').click();
+ cy.url().should('include', '/dashboard');
+ });
+
+ // Test with multiple case IDs per project
+ it(qase.projects({ PROJ1: [10, 11], PROJ2: [20] }, 'Checkout process works'), () => {
+ cy.visit('https://example.cypress.io/cart');
+ cy.get('#checkout-button').click();
+ cy.contains('Order successful').should('be.visible');
+ });
+
+ // Combining multi-project with other Qase methods
+ it(qase.projects({ PROJ1: [100], PROJ2: [200] }, 'User registration flow'), () => {
+ qase.title('Complete user registration with verification');
+ qase.fields({ severity: 'critical', priority: 'high' });
+
+ cy.visit('https://example.cypress.io/register');
+ cy.get('#email').type('newuser@example.com');
+ cy.get('#register-button').click();
+ cy.contains('Registration successful').should('be.visible');
+ });
+
+ // Single-project test (uses default_project)
+ it(qase(50, 'Test reported to default project'), () => {
+ cy.visit('https://example.cypress.io');
+ cy.contains('type').should('be.visible');
+ });
+
+ // Test without Qase metadata (goes to default_project without case ID)
+ it('Regular test without Qase tracking', () => {
+ cy.visit('https://example.cypress.io');
+ cy.contains('type').should('be.visible');
+ });
+});
+```
+
+---
+
## Troubleshooting
-### Results not appearing in projects
+### Results Not Appearing in All Projects
+
+* Ensure `mode` is `testops_multi` in reporter options
+* Verify project codes in `qase.projects()` match `testops_multi.projects[].code` exactly (case-sensitive)
+* For Cypress, ensure you pass the test to `qase.projects(mapping, it(...))` so the title is updated with markers, or use a title that already contains `(Qase PROJ: ids)`
+* Ensure each project has a valid API token with write permissions
+
+### Wrong Test Cases Linked
+
+* Check the `default_project` setting for tests without explicit mapping
+* Project codes are case-sensitive and must match exactly
+* Verify the mapping object has correct project codes as keys
+* Check that test case IDs exist in the respective projects
+* Enable debug logging to see how the reporter parses multi-project markers
+
+### Default Project Not Working
+
+* Ensure `default_project` is set in `testops_multi` config
+* Verify the default project code matches one of the projects in the `projects` array
+* Tests without `qase.projects()` will only report to the default project
-* Ensure `mode` is `testops_multi` in reporter options.
-* Verify project codes in `qase.projects()` match `testops_multi.projects[].code`.
-* For Cypress, ensure you pass the test to `qase.projects(mapping, it(...))` so the title is updated with markers, or use a title that already contains `(Qase PROJ: ids)`.
+---
-### Results sent to wrong project
+## See Also
-* Check the `default_project` setting for tests without explicit mapping.
-* Project codes are case-sensitive and must match exactly.
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Examples](../../examples/multiProject/cypress/)
diff --git a/qase-cypress/docs/STEPS.md b/qase-cypress/docs/STEPS.md
new file mode 100644
index 00000000..f796f822
--- /dev/null
+++ b/qase-cypress/docs/STEPS.md
@@ -0,0 +1,380 @@
+# Test Steps in Cypress
+
+This guide covers how to define and report test steps for detailed execution tracking in Qase.
+
+---
+
+## Overview
+
+Test steps provide granular visibility into test execution. Each step is reported separately, showing:
+
+- Step name and description
+- Step status (passed/failed)
+- Step duration
+- Attachments (if any)
+- Error details (on failure)
+
+---
+
+## Defining Steps
+
+### Using Step Callbacks
+
+Define steps using synchronous callbacks with Cypress commands:
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+describe('Test suite', () => {
+ it('Test with multiple steps', () => {
+ qase.step('Navigate to example page', () => {
+ cy.visit('https://example.cypress.io');
+ });
+
+ qase.step('Click on type link', () => {
+ cy.contains('type').click();
+ });
+
+ qase.step('Verify URL changed', () => {
+ cy.url().should('include', '/commands/actions');
+ });
+ });
+});
+```
+
+**Important:** Cypress steps use synchronous callbacks. Do not use `async`/`await` with Cypress steps.
+
+### Step Parameters
+
+Steps can include parameters for dynamic naming:
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+describe('Test suite', () => {
+ it('Test with dynamic step names', () => {
+ const username = 'john@example.com';
+
+ qase.step(`Login as user ${username}`, () => {
+ cy.get('#email').type(username);
+ cy.get('#password').type('password');
+ cy.get('button[type="submit"]').click();
+ });
+
+ qase.step(`Verify ${username} profile loaded`, () => {
+ cy.get('.user-email').should('contain', username);
+ });
+ });
+});
+```
+
+---
+
+## Nested Steps
+
+Create hierarchical step structures:
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+describe('Test suite', () => {
+ it('Test with nested steps', () => {
+ qase.step('Complete user registration', () => {
+ qase.step('Fill registration form', () => {
+ cy.get('#name').type('John Doe');
+ cy.get('#email').type('john@example.com');
+ });
+
+ qase.step('Submit registration', () => {
+ cy.get('button[type="submit"]').click();
+ });
+ });
+
+ qase.step('Verify registration success', () => {
+ cy.get('.success-message').should('be.visible');
+ });
+ });
+});
+```
+
+---
+
+## Steps with Expected Result and Data
+
+Define expected results and data for steps:
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+describe('Test suite', () => {
+ it('Test with expected results', () => {
+ qase.step(
+ 'Click button',
+ () => {
+ cy.get('#submit-button').click();
+ },
+ 'Button should be clicked',
+ 'Button data'
+ );
+
+ qase.step(
+ 'Fill form',
+ () => {
+ cy.get('#input-field').type('test value');
+ },
+ 'Form should be filled',
+ 'Form input data'
+ );
+
+ qase.step(
+ 'Submit form',
+ () => {
+ cy.get('button[type="submit"]').click();
+ },
+ 'Form should be submitted',
+ 'Form submission data'
+ );
+ });
+});
+```
+
+**Signature:**
+```typescript
+qase.step(
+ name: string,
+ callback: () => void,
+ expectedResult?: string,
+ data?: string
+): void
+```
+
+---
+
+## Steps with Attachments
+
+Attach content to a specific step:
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+describe('Test suite', () => {
+ it('Test with step attachments', () => {
+ qase.step('Capture application state', () => {
+ const state = JSON.stringify({ user: 'john', status: 'active' });
+
+ qase.attach({
+ name: 'app-state.json',
+ content: state,
+ contentType: 'application/json',
+ });
+ });
+
+ qase.step('Verify state', () => {
+ cy.get('.status').should('contain', 'active');
+ });
+ });
+});
+```
+
+---
+
+## Step Status
+
+Steps automatically inherit status from execution:
+
+| Execution | Step Status |
+|-----------|-------------|
+| Completes normally | Passed |
+| Throws Error/AssertionError | Failed |
+| Other exception | Invalid |
+
+---
+
+## Best Practices
+
+### Keep Steps Atomic
+
+Each step should represent a single action:
+
+```javascript
+// Good: One action per step
+qase.step('Click login button', () => {
+ cy.get('#login-btn').click();
+});
+
+qase.step('Enter username', () => {
+ cy.get('#username').type('user');
+});
+
+// Avoid: Multiple actions in one step
+qase.step('Fill form and submit', () => { // Too broad
+ cy.get('#username').type('user');
+ cy.get('#password').type('pass');
+ cy.get('#submit').click();
+});
+```
+
+### Use Descriptive Names
+
+```javascript
+// Good: Clear action description
+qase.step('Verify user is redirected to dashboard', () => {
+ cy.url().should('include', '/dashboard');
+});
+
+// Avoid: Vague names
+qase.step('Check page', () => {
+ cy.url().should('include', '/dashboard');
+});
+```
+
+### Include Context in Step Names
+
+```javascript
+// Good: Include relevant context
+qase.step(`Add product '${productName}' to cart`, () => {
+ cy.get(`[data-product="${productName}"] .add-to-cart`).click();
+});
+
+// Better than generic:
+qase.step('Add product', () => {
+ cy.get(`[data-product="${productName}"] .add-to-cart`).click();
+});
+```
+
+---
+
+## Common Patterns
+
+### Page Object Steps
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+class LoginPage {
+ login(username, password) {
+ qase.step(`Enter username: ${username}`, () => {
+ cy.get('#email').type(username);
+ });
+
+ qase.step('Enter password', () => {
+ cy.get('#password').type(password);
+ });
+
+ qase.step('Click login button', () => {
+ cy.get('button[type="submit"]').click();
+ });
+ }
+}
+
+describe('Authentication', () => {
+ it('User can login', () => {
+ const loginPage = new LoginPage();
+ cy.visit('/login');
+ loginPage.login('user@example.com', 'password');
+ });
+});
+```
+
+### API Testing Steps
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+describe('API tests', () => {
+ it('API returns correct user data', () => {
+ let response;
+
+ qase.step('Send GET request to /api/users/1', () => {
+ cy.request('GET', 'https://api.example.com/users/1').then((res) => {
+ response = res;
+ });
+ });
+
+ qase.step('Verify response status is 200', () => {
+ expect(response.status).to.equal(200);
+ });
+
+ qase.step('Verify response contains user data', () => {
+ expect(response.body.id).to.equal(1);
+ expect(response.body.name).to.exist;
+ });
+ });
+});
+```
+
+### Setup/Teardown Steps
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+describe('User tests', () => {
+ beforeEach(() => {
+ qase.step('Setup: Navigate to application', () => {
+ cy.visit('https://example.com');
+ });
+
+ qase.step('Setup: Authenticate user', () => {
+ cy.get('#email').type('test@example.com');
+ cy.get('#password').type('password');
+ cy.get('button[type="submit"]').click();
+ });
+ });
+
+ afterEach(() => {
+ qase.step('Cleanup: Logout user', () => {
+ cy.get('#logout-button').click();
+ });
+ });
+
+ it('Test user operations', () => {
+ qase.step('Perform user action', () => {
+ cy.get('#user-action').click();
+ });
+ });
+});
+```
+
+---
+
+## Troubleshooting
+
+### Steps Not Appearing
+
+1. Verify the step function is properly imported from `cypress-qase-reporter/mocha`
+2. Check that steps are executed within a test context
+3. Enable debug logging to trace step recording
+4. Ensure Cypress plugin is properly configured in `cypress.config.js`
+
+### Nested Steps Flattened
+
+Ensure you're using the synchronous callbacks correctly for nesting:
+
+```javascript
+// Correct: Nested callbacks
+qase.step('Parent step', () => {
+ qase.step('Child step', () => {
+ // Child step logic
+ });
+});
+
+// Incorrect: Sequential, not nested
+qase.step('Step 1', () => {
+ // Step 1 logic
+});
+qase.step('Step 2', () => { // Not nested under Step 1
+ // Step 2 logic
+});
+```
+
+### Step Duration Shows 0
+
+Steps need measurable execution time. Very fast steps may show 0ms duration.
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Attachments Guide](ATTACHMENTS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-cypress/docs/UPGRADE.md b/qase-cypress/docs/UPGRADE.md
new file mode 100644
index 00000000..11f8d4d8
--- /dev/null
+++ b/qase-cypress/docs/UPGRADE.md
@@ -0,0 +1,367 @@
+# Upgrade Guide: Cypress Reporter
+
+This guide covers migration steps between major versions of the Qase Cypress Reporter.
+
+---
+
+## Version History
+
+| Version | Release Date | Node.js Support | Key Changes |
+|---------|--------------|-----------------|-------------|
+| 3.2.0 | January 2026 | >= 14 | Current stable release with video attachment support |
+| 3.1.0 | December 2025 | >= 14 | Enhanced metadata handling and stability improvements |
+| 3.0.0 | August 2025 | >= 14 | **Major:** Video attachment support, improved configuration |
+| 2.3.1 | July 2025 | >= 14 | Final v2.x release with bug fixes |
+| 2.0.0 | March 2025 | >= 14 | Complete rewrite with new architecture |
+
+---
+
+## Upgrading to 3.x from 2.x
+
+Version 3.0.0 introduces video attachment support for Cypress tests, allowing automatic upload of test execution videos to Qase.
+
+### Breaking Changes
+
+1. **Configuration Structure:** New video-related configuration options added
+2. **Hook Setup:** New `afterSpecHook` required for video upload functionality
+3. **Attachment Handling:** Video attachments now use `preparedAttachments` for better management
+
+### Migration Steps
+
+#### 1. Update Package
+
+```bash
+npm install --save-dev cypress-qase-reporter@3.2.0
+```
+
+#### 2. Update Configuration
+
+**Before (v2.3.x):**
+
+```javascript
+// cypress.config.js
+const { defineConfig } = require('cypress');
+
+module.exports = defineConfig({
+ e2e: {
+ setupNodeEvents(on, config) {
+ require('cypress-qase-reporter/plugin')(on, config);
+ require('cypress-qase-reporter/metadata')(on);
+ },
+ },
+ reporter: 'cypress-qase-reporter',
+ reporterOptions: {
+ mode: 'testops',
+ testops: {
+ api: {
+ token: process.env.QASE_API_TOKEN,
+ },
+ project: 'DEMO',
+ },
+ },
+});
+```
+
+**After (v3.2.0):**
+
+```javascript
+// cypress.config.js
+const { defineConfig } = require('cypress');
+const { afterSpecHook } = require('cypress-qase-reporter/hooks');
+
+module.exports = defineConfig({
+ e2e: {
+ video: true, // Enable video recording
+ videosFolder: 'cypress/videos', // Specify video folder
+ setupNodeEvents(on, config) {
+ require('cypress-qase-reporter/plugin')(on, config);
+ require('cypress-qase-reporter/metadata')(on);
+
+ // Add afterSpecHook for video upload
+ on('after:spec', afterSpecHook(config));
+
+ return config;
+ },
+ },
+ reporter: 'cypress-qase-reporter',
+ reporterOptions: {
+ mode: 'testops',
+ testops: {
+ api: {
+ token: process.env.QASE_API_TOKEN,
+ },
+ project: 'DEMO',
+ uploadDelay: 1000, // Optional: delay before video upload (ms)
+ },
+ },
+});
+```
+
+#### 3. Enable Video Recording
+
+Ensure video recording is enabled in your Cypress configuration:
+
+```javascript
+{
+ e2e: {
+ video: true, // Must be true for video attachments
+ videosFolder: 'cypress/videos', // Default location
+ }
+}
+```
+
+#### 4. Update Test Annotations
+
+No changes required for test annotations. The Qase API remains unchanged:
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+describe('Test suite', () => {
+ it(qase(1, 'Test with video'), () => {
+ cy.visit('https://example.com');
+ cy.get('.button').click();
+ });
+});
+```
+
+---
+
+## Configuration Changes
+
+### New Options in v3.0.0
+
+| Option | Description | Default |
+|--------|-------------|---------|
+| `uploadDelay` | Delay (in milliseconds) before uploading video after test completion | 0 |
+
+**Example:**
+
+```javascript
+reporterOptions: {
+ mode: 'testops',
+ testops: {
+ api: { token: 'token' },
+ project: 'DEMO',
+ uploadDelay: 1000, // Wait 1 second before video upload
+ },
+}
+```
+
+### Video Upload Behavior
+
+- Videos are automatically attached to test results when video recording is enabled
+- Only videos for failed tests are uploaded by default (Cypress behavior)
+- Configure `videoUploadOnPasses: true` in Cypress config to upload videos for passing tests
+- Videos are uploaded after the spec file completes execution
+
+---
+
+## API Changes
+
+### No Breaking API Changes
+
+The Qase test annotation API remains fully backward compatible:
+
+```javascript
+// All existing patterns work in v3.x
+import { qase } from 'cypress-qase-reporter/mocha';
+
+describe('Tests', () => {
+ it(qase(1, 'Single ID'), () => { /* ... */ });
+ it(qase([1, 2], 'Multiple IDs'), () => { /* ... */ });
+});
+
+it('Test with metadata', () => {
+ qase.title('Custom title');
+ qase.fields({ severity: 'high' });
+ qase.attach({ paths: 'screenshot.png' });
+});
+```
+
+### New Internal Methods
+
+These methods are used internally by the reporter and do not require direct usage:
+
+- `uploadAttachment()` - Handles attachment upload to Qase
+- `preparedAttachments` - Property for managing attachments before upload
+
+---
+
+## Before/After Examples
+
+### Example 1: Basic Configuration
+
+**Before (v2.3.x):**
+
+```javascript
+const { defineConfig } = require('cypress');
+
+module.exports = defineConfig({
+ e2e: {
+ setupNodeEvents(on, config) {
+ require('cypress-qase-reporter/plugin')(on, config);
+ require('cypress-qase-reporter/metadata')(on);
+ },
+ },
+ reporter: 'cypress-qase-reporter',
+ reporterOptions: {
+ mode: 'testops',
+ testops: {
+ api: { token: process.env.QASE_API_TOKEN },
+ project: 'DEMO',
+ },
+ },
+});
+```
+
+**After (v3.2.0):**
+
+```javascript
+const { defineConfig } = require('cypress');
+const { afterSpecHook } = require('cypress-qase-reporter/hooks');
+
+module.exports = defineConfig({
+ e2e: {
+ video: true,
+ setupNodeEvents(on, config) {
+ require('cypress-qase-reporter/plugin')(on, config);
+ require('cypress-qase-reporter/metadata')(on);
+ on('after:spec', afterSpecHook(config));
+ return config;
+ },
+ },
+ reporter: 'cypress-qase-reporter',
+ reporterOptions: {
+ mode: 'testops',
+ testops: {
+ api: { token: process.env.QASE_API_TOKEN },
+ project: 'DEMO',
+ },
+ },
+});
+```
+
+### Example 2: Test with Attachments
+
+**Before (v2.3.x):**
+
+```javascript
+it(qase(1, 'Test with screenshot'), () => {
+ cy.visit('https://example.com');
+ cy.screenshot('my-screenshot');
+ // Screenshot attached via Cypress integration
+});
+```
+
+**After (v3.2.0):**
+
+```javascript
+it(qase(1, 'Test with screenshot and video'), () => {
+ cy.visit('https://example.com');
+ cy.screenshot('my-screenshot');
+ // Screenshot AND video automatically attached
+});
+```
+
+---
+
+## Compatibility Notes
+
+### Node.js Version Support
+
+- **v3.2.0:** Node.js >= 14
+- **v2.3.x:** Node.js >= 14
+
+### Cypress Version Support
+
+- **v3.2.0:** Cypress >= 10.0.0
+- **v2.3.x:** Cypress >= 8.0.0
+
+**Note:** Cypress 10+ uses the new configuration format with `setupNodeEvents`. If upgrading from Cypress 9 or earlier, you'll also need to migrate your Cypress configuration.
+
+### Framework Compatibility
+
+- CommonJS and ES Modules supported
+- TypeScript support with full type definitions
+- Works with Cypress Component Testing and E2E Testing
+- Compatible with Cypress Cloud (formerly Dashboard)
+
+---
+
+## Troubleshooting
+
+### Common Migration Issues
+
+#### Issue: Videos not uploading to Qase
+
+**Solution:**
+
+1. Verify video recording is enabled: `video: true` in config
+2. Check that `afterSpecHook` is registered:
+ ```javascript
+ const { afterSpecHook } = require('cypress-qase-reporter/hooks');
+ on('after:spec', afterSpecHook(config));
+ ```
+3. Ensure videos folder exists and is writable: `videosFolder: 'cypress/videos'`
+
+#### Issue: Hook registration error
+
+**Solution:** Ensure you're importing from the correct path:
+
+```javascript
+// Correct
+const { afterSpecHook } = require('cypress-qase-reporter/hooks');
+
+// Incorrect
+const { afterSpecHook } = require('cypress-qase-reporter');
+```
+
+#### Issue: Configuration not recognized
+
+**Solution:** Verify the `reporterOptions` structure includes the `TestOps` object:
+
+```javascript
+reporterOptions: {
+ mode: 'testops',
+ testops: {
+ api: { token: 'token' },
+ project: 'DEMO',
+ },
+}
+```
+
+#### Issue: Module not found after upgrade
+
+**Solution:** Clear node_modules and reinstall:
+
+```bash
+rm -rf node_modules package-lock.json
+npm install
+```
+
+---
+
+## Getting Help
+
+If you encounter issues during migration:
+
+1. Check the [GitHub Issues](https://github.com/qase-tms/qase-javascript/issues)
+2. Review the CHANGELOG
+3. Open a new issue with:
+ - Previous version (e.g., 2.3.1)
+ - Target version (3.2.0)
+ - Cypress version
+ - Error messages
+ - Configuration file (without sensitive data)
+ - Steps to reproduce
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Multi-Project Support](MULTI_PROJECT.md)
+- CHANGELOG
+- [Cypress Configuration Migration Guide](https://docs.cypress.io/guides/references/migration-guide)
diff --git a/qase-cypress/docs/cucumber.md b/qase-cypress/docs/cucumber.md
index 7e1461f6..19ad1b65 100644
--- a/qase-cypress/docs/cucumber.md
+++ b/qase-cypress/docs/cucumber.md
@@ -268,8 +268,8 @@ The Qase reporter will automatically extract these IDs and link the test results
Check out our example projects:
-- **[@badeball/cypress-cucumber-preprocessor Example](../../examples/cypressBadeballCucumber/)** - Modern implementation with automatic step reporting
-- **[cypress-cucumber-preprocessor Example](../../examples/cypressCucumber/)** - Legacy implementation
+- **[@badeball/cypress-cucumber-preprocessor Example](../../examples/single/cypressBadeballCucumber/)** - Modern implementation with automatic step reporting
+- **[cypress-cucumber-preprocessor Example](../../examples/single/cypressCucumber/)** - Legacy implementation
Each example includes:
- Complete Cypress configuration
@@ -317,7 +317,7 @@ If you see errors like "Cypress detected that you returned a promise from a comm
- Verify your API token is correct in `cypress.config.js`
- Check that your project code matches your Qase project
- Enable `debug: true` in reporter options to see detailed logs
-- Ensure `mode: 'testops'` is set in your configuration
+- Ensure `mode: 'TestOps'` is set in your configuration
---
diff --git a/qase-cypress/docs/usage.md b/qase-cypress/docs/usage.md
index 4569152f..702d6a08 100644
--- a/qase-cypress/docs/usage.md
+++ b/qase-cypress/docs/usage.md
@@ -1,258 +1,1107 @@
# Qase Integration in Cypress
-This guide demonstrates how to integrate Qase with Cypress, providing instructions on how to add Qase IDs, titles,
-fields, suites, comments, and file attachments to your test cases.
+This guide provides comprehensive instructions for integrating Qase with Cypress.
+> **Configuration:** For complete configuration reference including all available options, environment variables, and examples, see the [qase-javascript-commons README](../../qase-javascript-commons/README.md#configuration).
+
+---
+
+## Table of Contents
+
+- [Adding QaseID](#adding-qaseid)
+- [Adding Title](#adding-title)
+- [Adding Fields](#adding-fields)
+- [Adding Suite](#adding-suite)
+- [Ignoring Tests](#ignoring-tests)
+- [Muting Tests](#muting-tests)
+- [Working with Attachments](#working-with-attachments)
+- [Working with Steps](#working-with-steps)
+- [Working with Parameters](#working-with-parameters)
+- [Multi-Project Support](#multi-project-support)
+- [Running Tests](#running-tests)
+- [Integration Patterns](#integration-patterns)
+- [Common Use Cases](#common-use-cases)
+- [Troubleshooting](#troubleshooting)
+- [Complete Examples](#complete-examples)
+
+- [See Also](#see-also)
---
-## Adding QaseID to a Test
+## Adding QaseID
-To associate a QaseID with a test in Cypress, use the `qase` function. This function accepts a single integer
-representing the test's ID in Qase.
+Link your automated tests to existing test cases in Qase by specifying the test case ID.
-### Example:
+### Single ID
```javascript
import { qase } from 'cypress-qase-reporter/mocha';
-qase(1, it('simple test', () => {
- cy.visit('https://example.com');
-}));
+describe('Authentication', () => {
+ it(qase(1, 'User can login with valid credentials'), () => {
+ cy.visit('https://example.com/login');
+ cy.get('#email').type('user@example.com');
+ cy.get('#password').type('password123');
+ cy.get('button[type="submit"]').click();
+ cy.url().should('include', '/dashboard');
+ });
+});
```
----
+### Multiple IDs
-## Adding a Title to a Test
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
-You can provide a title for your test using the `qase.title` function. The function accepts a string, which will be
-used as the test's title in Qase. If no title is provided, the test method name will be used by default.
+describe('Cross-Browser Testing', () => {
+ it(qase([1, 2, 3], 'Login works across Chrome, Firefox, and Edge'), () => {
+ cy.visit('https://example.com/login');
+ cy.get('#email').type('user@example.com');
+ cy.get('button[type="submit"]').click();
+ });
+});
+```
+
+### Multi-Project Support
+
+To send test results to multiple Qase projects simultaneously, see the [Multi-Project Support Guide](MULTI_PROJECT.md).
-### Example:
+---
+
+## Adding Title
+
+Set a custom title for the test case (overrides auto-generated title):
```javascript
import { qase } from 'cypress-qase-reporter/mocha';
-it('test', () => {
- qase.title('Title');
- cy.visit('https://example.com');
+describe('Login Tests', () => {
+ it('login test', () => {
+ qase.title('User successfully logs in with valid credentials');
+
+ cy.visit('https://example.com/login');
+ cy.get('#email').type('user@example.com');
+ cy.get('#password').type('password123');
+ cy.get('button[type="submit"]').click();
+ });
});
```
---
-## Adding Fields to a Test
+## Adding Fields
-The `qase.fields` function allows you to add additional metadata to a test case. You can specify multiple fields to
-enhance test case information in Qase.
+Add metadata to your test cases using fields. Both system and custom fields are supported.
-### System Fields:
+### System Fields
-- `description` — Description of the test case.
-- `preconditions` — Preconditions for the test case.
-- `postconditions` — Postconditions for the test case.
-- `severity` — Severity of the test case (e.g., `critical`, `major`).
-- `priority` — Priority of the test case (e.g., `high`, `low`).
-- `layer` — Test layer (e.g., `UI`, `API`).
+| Field | Description | Example Values |
+|-------|-------------|----------------|
+| `description` | Test case description | Any text |
+| `preconditions` | Test preconditions | Any text (supports Markdown) |
+| `postconditions` | Test postconditions | Any text |
+| `severity` | Test severity | `blocker`, `critical`, `major`, `normal`, `minor`, `trivial` |
+| `priority` | Test priority | `high`, `medium`, `low` |
+| `layer` | Test layer | `e2e`, `api`, `unit` |
-### Example:
+### Example
```javascript
import { qase } from 'cypress-qase-reporter/mocha';
-it('test', () => {
- qase.fields({ description: "Description", preconditions: "Preconditions" });
- cy.visit('https://example.com');
+describe('User Management', () => {
+ it('create new user', () => {
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'e2e',
+ description: 'Verifies that admin can create a new user account',
+ preconditions: 'Admin user is logged in',
+ postconditions: 'New user appears in user list',
+ });
+
+ cy.visit('/admin/users');
+ cy.get('[data-testid="create-user-btn"]').click();
+ cy.get('#username').type('newuser');
+ cy.get('#email').type('newuser@example.com');
+ cy.get('button[type="submit"]').click();
+ cy.contains('User created successfully').should('be.visible');
+ });
});
```
---
-## Adding a Suite to a Test
+## Adding Suite
-To assign a suite or sub-suite to a test, use the `qase.suite` function. It can receive a suite name, and optionally a
-sub-suite, both as strings.
+Organize tests into suites and sub-suites:
-### Example:
+### Simple Suite
```javascript
import { qase } from 'cypress-qase-reporter/mocha';
-it('test', () => {
- qase.suite("Suite 01");
- cy.visit('https://example.com');
+describe('Payment Tests', () => {
+ it('process payment', () => {
+ qase.suite('E2E Tests / Payment');
+
+ cy.visit('/checkout');
+ cy.get('#card-number').type('4242424242424242');
+ cy.get('button[type="submit"]').click();
+ });
});
+```
-it('test', () => {
- qase.suite("Suite 01\tSuite 02");
- cy.visit('https://example.com');
+### Nested Suites
+
+```javascript
+describe('User Tests', () => {
+ it('user registration', () => {
+ qase.suite('E2E Tests\tUser Management\tRegistration');
+
+ cy.visit('/register');
+ cy.get('#email').type('user@example.com');
+ cy.get('#password').type('securePassword123');
+ cy.get('button[type="submit"]').click();
+ });
});
```
---
-## Ignoring a Test in Qase
+## Ignoring Tests
+
+Exclude a test from Qase reporting. The test still executes, but results are not sent to Qase:
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+describe('Feature Tests', () => {
+ it('test under development', () => {
+ qase.ignore();
+
+ cy.visit('/new-feature');
+ cy.get('.feature-toggle').click();
+ });
+});
+```
+
+---
-To exclude a test from being reported to Qase (while still executing the test in Cypress), use the `qase.ignore`
-function. The test will run, but its result will not be sent to Qase.
+## Muting Tests
-### Example:
+Mark a test as muted. Muted tests are reported but do not affect the test run status:
```javascript
import { qase } from 'cypress-qase-reporter/mocha';
-it('test', () => {
- qase.ignore();
- cy.visit('https://example.com');
+describe('Known Issues', () => {
+ it('flaky test with known issue', () => {
+ qase.mute();
+
+ cy.visit('/dashboard');
+ cy.get('.chart').should('be.visible');
+ });
});
```
---
-## Adding a Comment to a Test
+## Working with Attachments
-You can attach comments to the test results in Qase using the `qase.comment` function. The comment will be displayed
-alongside the test execution details in Qase.
+Attach files, screenshots, logs, and other content to your test results.
-### Example:
+### Attach File from Path
```javascript
import { qase } from 'cypress-qase-reporter/mocha';
-it('test', () => {
- qase.comment("Some comment");
- cy.visit('https://example.com');
+describe('File Upload Tests', () => {
+ it('upload document', () => {
+ qase.attach({ paths: 'cypress/fixtures/document.pdf' });
+
+ cy.visit('/upload');
+ cy.get('input[type="file"]').selectFile('cypress/fixtures/document.pdf');
+ cy.get('button[type="submit"]').click();
+ });
+});
+```
+
+### Attach Multiple Files
+
+```javascript
+it('test with multiple attachments', () => {
+ qase.attach({
+ paths: [
+ 'cypress/fixtures/data.json',
+ 'cypress/fixtures/config.yml',
+ 'cypress/fixtures/image.png',
+ ],
+ });
+
+ cy.visit('/dashboard');
+});
+```
+
+### Attach Content from Code
+
+```javascript
+it('test with log attachment', () => {
+ const testLog = `
+ Test execution log
+ Step 1: Visited login page
+ Step 2: Entered credentials
+ Step 3: Clicked submit button
+ `;
+
+ qase.attach({
+ name: 'execution.log',
+ content: testLog,
+ contentType: 'text/plain',
+ });
+
+ cy.visit('/login');
+});
+```
+
+### Attach JSON Data
+
+```javascript
+it('API test with response data', () => {
+ cy.request('GET', 'https://api.example.com/users/1').then((response) => {
+ qase.attach({
+ name: 'api-response.json',
+ content: JSON.stringify(response.body, null, 2),
+ contentType: 'application/json',
+ });
+
+ expect(response.status).to.equal(200);
+ });
+});
+```
+
+### Attach Screenshot
+
+Cypress automatically captures screenshots on failure. To manually capture and attach:
+
+```javascript
+it('capture custom screenshot', () => {
+ cy.visit('/dashboard');
+ cy.wait(1000);
+
+ cy.screenshot('dashboard-loaded', { capture: 'fullPage' });
+
+ qase.attach({
+ paths: 'cypress/screenshots/dashboard-loaded.png',
+ });
+});
+```
+
+### Attach to Specific Step
+
+```javascript
+it('test with step attachments', () => {
+ qase.step('Load dashboard', () => {
+ cy.visit('/dashboard');
+
+ cy.screenshot('dashboard-view');
+ qase.attach({
+ paths: 'cypress/screenshots/dashboard-view.png',
+ });
+ });
+
+ qase.step('Verify chart rendering', () => {
+ cy.get('.chart').should('be.visible');
+ });
});
```
+### Supported MIME Types
+
+Common MIME types are auto-detected. You can also specify explicitly:
+
+| Extension | MIME Type |
+|-----------|-----------|
+| `.png` | `image/png` |
+| `.jpg`, `.jpeg` | `image/jpeg` |
+| `.gif` | `image/gif` |
+| `.txt` | `text/plain` |
+| `.json` | `application/json` |
+| `.xml` | `application/xml` |
+| `.html` | `text/html` |
+| `.pdf` | `application/pdf` |
+
+> For more details, see [Attachments Guide](ATTACHMENTS.md).
+
---
-## Attaching Files to a Test
+## Working with Steps
-To attach files to a test result, use the `qase.attach` function. This method supports attaching one or multiple files,
-along with optional file names, comments, and file types.
+Define test steps for detailed reporting in Qase.
-### Example:
+### Using Synchronous Function
+
+Cypress steps use synchronous callbacks (no async/await needed):
```javascript
import { qase } from 'cypress-qase-reporter/mocha';
-it('test', () => {
- qase.attach({ name: 'attachment.txt', content: 'Hello, world!', contentType: 'text/plain' });
- qase.attach({ paths: '/path/to/file' });
- qase.attach({ paths: ['/path/to/file', '/path/to/another/file'] });
- cy.visit('https://example.com');
+describe('Shopping Cart', () => {
+ it('add item to cart', () => {
+ qase.step('Navigate to product page', () => {
+ cy.visit('/products/laptop');
+ cy.contains('Add to Cart').should('be.visible');
+ });
+
+ qase.step('Add product to cart', () => {
+ cy.get('[data-testid="add-to-cart"]').click();
+ cy.contains('Added to cart').should('be.visible');
+ });
+
+ qase.step('Verify cart count', () => {
+ cy.get('[data-testid="cart-count"]').should('contain', '1');
+ });
+ });
+});
+```
+
+### Nested Steps
+
+```javascript
+it('complete checkout process', () => {
+ qase.step('Add items to cart', () => {
+ qase.step('Add first item', () => {
+ cy.visit('/products/laptop');
+ cy.get('[data-testid="add-to-cart"]').click();
+ });
+
+ qase.step('Add second item', () => {
+ cy.visit('/products/mouse');
+ cy.get('[data-testid="add-to-cart"]').click();
+ });
+ });
+
+ qase.step('Complete checkout', () => {
+ qase.step('Enter shipping info', () => {
+ cy.visit('/checkout');
+ cy.get('#address').type('123 Main St');
+ });
+
+ qase.step('Submit payment', () => {
+ cy.get('#card-number').type('4242424242424242');
+ cy.get('button[type="submit"]').click();
+ });
+ });
});
```
-## Adding Parameters to a Test
+### Steps with Expected Result
-You can add parameters to a test case using the `qase.parameters` function. This function accepts an object with
-parameter names and values.
+```javascript
+it('form validation test', () => {
+ qase.step(
+ 'Enter invalid email',
+ () => {
+ cy.get('#email').type('invalid-email');
+ cy.get('#email').blur();
+ },
+ 'Validation error message should appear',
+ );
+
+ qase.step(
+ 'Verify error message',
+ () => {
+ cy.contains('Please enter a valid email').should('be.visible');
+ },
+ 'Error message is displayed correctly',
+ );
+});
+```
+
+> For more details, see [Steps Guide](STEPS.md).
+
+---
-### Example:
+## Working with Parameters
+
+Report parameterized test data to Qase.
+
+### Basic Parameterized Test
```javascript
import { qase } from 'cypress-qase-reporter/mocha';
-it('test', () => {
- qase.parameters({ param1: 'value1', param2: 'value2' });
- cy.visit('https://example.com');
+describe('Login with different users', () => {
+ const users = [
+ { username: 'admin', password: 'admin123', role: 'admin' },
+ { username: 'user', password: 'user123', role: 'user' },
+ { username: 'guest', password: 'guest123', role: 'guest' },
+ ];
+
+ users.forEach((user) => {
+ it(`login as ${user.role}`, () => {
+ qase.parameters({
+ username: user.username,
+ role: user.role,
+ });
+
+ cy.visit('/login');
+ cy.get('#username').type(user.username);
+ cy.get('#password').type(user.password);
+ cy.get('button[type="submit"]').click();
+ cy.url().should('include', '/dashboard');
+ });
+ });
+});
+```
+
+### Group Parameters
+
+```javascript
+describe('Cross-browser tests', () => {
+ const browsers = ['chrome', 'firefox', 'edge'];
+
+ browsers.forEach((browser) => {
+ it(`test in ${browser}`, () => {
+ qase.parameters({
+ browser: browser,
+ environment: 'staging',
+ viewport: '1920x1080',
+ });
+
+ qase.groupParameters({
+ 'Test Group': 'Cross-browser',
+ 'Priority': 'High',
+ });
+
+ cy.visit('/');
+ cy.get('.hero').should('be.visible');
+ });
+ });
});
```
-## Adding Group Parameters to a Test
+---
+
+## Multi-Project Support
-To add group parameters to a test case, use the `qase.groupParameters` function. This function accepts an list with
-group parameter names.
+Send test results to multiple Qase projects simultaneously with different test case IDs for each project.
-### Example:
+For detailed configuration, examples, and troubleshooting, see the [Multi-Project Support Guide](MULTI_PROJECT.md).
+
+---
+
+## Integration Patterns
+
+### Custom Commands with Qase
+
+Create reusable Cypress custom commands that integrate with Qase reporting:
```javascript
-import { qase } from 'cypress-qase-reporter/mocha';
+// cypress/support/commands.js
+Cypress.Commands.add('loginWithQase', (username, password) => {
+ qase.step('Login via custom command', () => {
+ cy.visit('/login');
+ cy.get('#username').type(username);
+ cy.get('#password').type(password);
+ cy.get('button[type="submit"]').click();
+ cy.url().should('include', '/dashboard');
+ });
+});
+
+// In your test
+it('login using custom command', () => {
+ cy.loginWithQase('testuser', 'password123');
+});
+```
+
+### Intercept API Calls with Reporting
+
+```javascript
+it('verify API call during user action', () => {
+ cy.intercept('POST', '/api/users').as('createUser');
+
+ qase.step('Fill registration form', () => {
+ cy.visit('/register');
+ cy.get('#email').type('newuser@example.com');
+ cy.get('#password').type('password123');
+ cy.get('button[type="submit"]').click();
+ });
-it('test', () => {
- qase.parameters({ param1: 'value1', param2: 'value2' });
- qase.groupParameters(['param1']);
- cy.visit('https://example.com');
+ qase.step('Verify API request', () => {
+ cy.wait('@createUser').then((interception) => {
+ expect(interception.response.statusCode).to.equal(201);
+
+ qase.attach({
+ name: 'api-request.json',
+ content: JSON.stringify(interception.request.body, null, 2),
+ contentType: 'application/json',
+ });
+
+ qase.attach({
+ name: 'api-response.json',
+ content: JSON.stringify(interception.response.body, null, 2),
+ contentType: 'application/json',
+ });
+ });
+ });
});
```
-## Adding Steps to a Test
+### Fixture-Based Test Data
-You can add steps to a test case using the `qase.step` function. This function accepts a string, which will be used as
-the step description in Qase.
+```javascript
+describe('User CRUD operations', () => {
+ it('create user with fixture data', () => {
+ cy.fixture('users/admin.json').then((userData) => {
+ qase.parameters({
+ 'User Type': userData.role,
+ 'Permissions': userData.permissions.join(', '),
+ });
+
+ qase.step('Create user with fixture data', () => {
+ cy.visit('/admin/users/new');
+ cy.get('#email').type(userData.email);
+ cy.get('#role').select(userData.role);
+ cy.get('button[type="submit"]').click();
+ });
+ });
+ });
+});
+```
-### Example:
+### Before/After Hooks
```javascript
-import { qase } from 'cypress-qase-reporter/mocha';
+describe('Test Suite with Hooks', () => {
+ before(() => {
+ qase.comment('Suite-level setup: Database seeded with test data');
+ cy.task('seedDatabase');
+ });
+
+ after(() => {
+ qase.comment('Suite-level teardown: Test data cleaned up');
+ cy.task('cleanDatabase');
+ });
+
+ beforeEach(() => {
+ cy.visit('/');
+ });
-it('test', () => {
- qase.step('Some step', () => {
- // some actions
+ it('test with hooks', () => {
+ cy.get('.welcome').should('be.visible');
});
- cy.visit('https://example.com');
+});
+```
+
+### Page Object Pattern
+
+```javascript
+// cypress/support/pages/LoginPage.js
+export class LoginPage {
+ visit() {
+ cy.visit('/login');
+ }
+
+ fillUsername(username) {
+ cy.get('#username').type(username);
+ }
+
+ fillPassword(password) {
+ cy.get('#password').type(password);
+ }
+
+ submit() {
+ cy.get('button[type="submit"]').click();
+ }
+
+ login(username, password) {
+ qase.step(`Login as ${username}`, () => {
+ this.visit();
+ this.fillUsername(username);
+ this.fillPassword(password);
+ this.submit();
+ });
+ }
+}
+
+// In your test
+import { LoginPage } from '../support/pages/LoginPage';
+
+it('login using page object', () => {
+ const loginPage = new LoginPage();
+ loginPage.login('testuser', 'password123');
+
+ cy.url().should('include', '/dashboard');
});
```
---
-## Cucumber Steps Reporting
+## Common Use Cases
+
+### Report API Test Results
+
+```javascript
+describe('API Tests', () => {
+ it(qase(10, 'GET request returns user data'), () => {
+ cy.request('GET', 'https://api.example.com/users/1').then((response) => {
+ qase.fields({
+ layer: 'api',
+ severity: 'critical',
+ });
+
+ expect(response.status).to.equal(200);
+ expect(response.body).to.have.property('id', 1);
+
+ qase.attach({
+ name: 'response.json',
+ content: JSON.stringify(response.body, null, 2),
+ contentType: 'application/json',
+ });
+ });
+ });
+});
+```
+
+### Attach Screenshot on Failure
+
+```javascript
+describe('Visual Tests', () => {
+ afterEach(function() {
+ if (this.currentTest.state === 'failed') {
+ const testName = this.currentTest.title.replace(/\s+/g, '-');
+ cy.screenshot(`failure-${testName}`);
+ }
+ });
+
+ it(qase(11, 'Dashboard displays correctly'), () => {
+ cy.visit('/dashboard');
+ cy.get('.chart').should('be.visible');
+ cy.get('.stats').should('contain', 'Total Users');
+ });
+});
+```
+
+### Use with Cucumber Preprocessor
-### For @badeball/cypress-cucumber-preprocessor
+For Cucumber/Gherkin integration, see the [Cucumber Integration Guide](cucumber.md).
-When using `@badeball/cypress-cucumber-preprocessor`, you can automatically report Cucumber/Gherkin steps to Qase using the `addCucumberStep` function in a `BeforeStep` hook.
+**Quick example:**
-#### Automatic Step Reporting
+```gherkin
+# features/login.feature
+@QaseID=12
+Feature: User Login
+ As a user
+ I want to log in to the application
+ So I can access my account
-Create a file `cypress/support/step_definitions/hooks.js`:
+ Scenario: Successful login
+ Given I am on the login page
+ When I enter valid credentials
+ And I click the login button
+ Then I should see the dashboard
+```
+
+### Report Visual Regression Results
```javascript
-import { BeforeStep } from '@badeball/cypress-cucumber-preprocessor';
-import { addCucumberStep } from 'cypress-qase-reporter/cucumber';
+describe('Visual Regression Tests', () => {
+ it(qase(13, 'Homepage matches baseline'), () => {
+ cy.visit('/');
+
+ qase.step('Capture homepage screenshot', () => {
+ cy.screenshot('homepage', { capture: 'fullPage' });
+ });
+
+ qase.step('Compare with baseline', () => {
+ cy.task('compareScreenshots', {
+ actual: 'homepage.png',
+ baseline: 'baseline/homepage.png',
+ }).then((result) => {
+ qase.attach({
+ name: 'comparison-result.json',
+ content: JSON.stringify(result, null, 2),
+ contentType: 'application/json',
+ });
+
+ expect(result.mismatchPercentage).to.be.below(0.1);
+ });
+ });
+ });
+});
+```
-// Automatically report each Gherkin step to Qase
-BeforeStep(function({ pickleStep }) {
- const keyword = pickleStep.keyword || '';
- const stepText = `${keyword}${pickleStep.text}`;
- addCucumberStep(stepText);
+### Test with Multiple Assertions
+
+```javascript
+it(qase(14, 'User profile displays all information'), () => {
+ qase.step('Navigate to profile page', () => {
+ cy.visit('/profile');
+ });
+
+ qase.step('Verify profile information', () => {
+ cy.get('[data-testid="user-name"]').should('contain', 'John Doe');
+ cy.get('[data-testid="user-email"]').should('contain', 'john@example.com');
+ cy.get('[data-testid="user-role"]').should('contain', 'Admin');
+ cy.get('[data-testid="user-status"]').should('contain', 'Active');
+ });
+
+ qase.fields({
+ severity: 'normal',
+ priority: 'medium',
+ });
});
```
-This will automatically capture each step (Given/When/Then/And/But) and report it to Qase with its execution status.
+### Data-Driven Testing
-#### Manual Step Reporting
+```javascript
+describe('Form Validation', () => {
+ const invalidEmails = [
+ { email: 'notanemail', error: 'Please enter a valid email' },
+ { email: '@example.com', error: 'Please enter a valid email' },
+ { email: 'user@', error: 'Please enter a valid email' },
+ { email: '', error: 'Email is required' },
+ ];
+
+ invalidEmails.forEach(({ email, error }) => {
+ it(`validates email: "${email}"`, () => {
+ qase.parameters({
+ 'Input Email': email,
+ 'Expected Error': error,
+ });
+
+ cy.visit('/register');
+ cy.get('#email').type(email);
+ cy.get('#email').blur();
+ cy.contains(error).should('be.visible');
+ });
+ });
+});
+```
-Alternatively, you can manually add steps in your step definitions:
+### Environment-Specific Testing
```javascript
-import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor';
-import { addCucumberStep } from 'cypress-qase-reporter/cucumber';
+describe('Environment-specific tests', () => {
+ it(qase(15, 'Test runs in correct environment'), () => {
+ const env = Cypress.env('ENVIRONMENT') || 'staging';
+
+ qase.parameters({
+ Environment: env,
+ BaseURL: Cypress.config('baseUrl'),
+ });
+
+ qase.comment(`Test executed in ${env} environment`);
-Given('I am on the homepage', () => {
- addCucumberStep('Given I am on the homepage');
- cy.visit('https://example.com');
+ cy.visit('/');
+ cy.get('h1').should('be.visible');
+ });
});
+```
+
+---
+
+## Running Tests
+
+### Basic Execution
+
+```bash
+# Run all tests
+npx cypress run
+
+# Run in headed mode
+npx cypress open
+```
+
+### With Environment Variables
+
+```bash
+# Enable Qase reporting
+QASE_MODE=testops npx cypress run
+
+# Specify project code
+QASE_MODE=testops QASE_TESTOPS_PROJECT=DEMO npx cypress run
+
+# Use specific API token
+QASE_MODE=testops QASE_TESTOPS_API_TOKEN=your_token npx cypress run
+```
+
+### With Test Plan
+
+```bash
+# Run tests linked to specific test plan
+QASE_MODE=testops QASE_TESTOPS_PLAN_ID=123 npx cypress run
+```
+
+### With Existing Test Run
+
+```bash
+# Report to existing test run
+QASE_MODE=testops QASE_TESTOPS_RUN_ID=456 npx cypress run
+```
+
+### Run Specific Tests
+
+```bash
+# Run specific spec file
+npx cypress run --spec "cypress/e2e/login.cy.js"
+
+# Run multiple spec files
+npx cypress run --spec "cypress/e2e/login.cy.js,cypress/e2e/register.cy.js"
+
+# Run specs matching pattern
+npx cypress run --spec "cypress/e2e/auth/**/*.cy.js"
+```
+
+---
+
+## Troubleshooting
+
+### Tests Not Appearing in Qase
+
+**Issue:** Tests execute but results don't appear in Qase TestOps.
+
+**Solutions:**
-When('I click the button', () => {
- addCucumberStep('When I click the button');
+1. Verify `mode` is set to `TestOps` (not `off` or `report`)
+2. Check API token has write permissions
+3. Verify project code is correct
+4. Check for errors in Cypress console output
+5. Enable debug logging: set `debug: true` in configuration
+
+```javascript
+// cypress.config.js
+module.exports = defineConfig({
+ reporterOptions: {
+ debug: true,
+ mode: 'testops',
+ },
+});
+```
+
+### setupNodeEvents Not Configured
+
+**Issue:** Error: "Qase reporter requires setupNodeEvents configuration"
+
+**Solution:** Add plugin and metadata initialization in `cypress.config.js`:
+
+```javascript
+const { defineConfig } = require('cypress');
+
+module.exports = defineConfig({
+ e2e: {
+ setupNodeEvents(on, config) {
+ require('cypress-qase-reporter/plugin')(on, config);
+ require('cypress-qase-reporter/metadata')(on);
+ },
+ },
+});
+```
+
+### Attachments Not Uploading
+
+**Issue:** Screenshots or files not appearing in test results.
+
+**Solutions:**
+
+1. Verify file path exists and is readable
+2. Check `uploadAttachments: true` in configuration
+3. Ensure file size is within limits
+4. Enable debug logging to see upload status
+
+```javascript
+reporterOptions: {
+ testops: {
+ uploadAttachments: true,
+ },
+}
+```
+
+### Cypress Commands in Step Callbacks
+
+**Issue:** Steps complete immediately without waiting for Cypress commands.
+
+**Solution:** Cypress commands are asynchronous. Steps should contain Cypress commands directly:
+
+```javascript
+// Correct
+qase.step('Click button', () => {
cy.get('button').click();
+ cy.contains('Success').should('be.visible');
});
-Then('I should see the result', () => {
- addCucumberStep('Then I should see the result');
- cy.get('.result').should('be.visible');
+// Incorrect - don't use await
+qase.step('Click button', async () => {
+ await cy.get('button').click(); // Don't do this
});
```
-### For cypress-cucumber-preprocessor (legacy)
+### Results Going to Wrong Test Cases
+
+**Issue:** Test results appear under incorrect test case IDs.
+
+**Solutions:**
+
+1. Verify QaseID matches the test case ID in Qase
+2. Check for duplicate IDs in your test suite
+3. Verify you're using the correct project code
+4. Ensure test names haven't changed significantly
+
+### Headless Mode Issues
+
+**Issue:** Tests pass in headed mode but fail in headless mode.
+
+**Solutions:**
+
+1. Add explicit waits for elements:
+ ```javascript
+ cy.get('.button', { timeout: 10000 }).should('be.visible').click();
+ ```
+
+2. Wait for network requests:
+ ```javascript
+ cy.intercept('GET', '/api/data').as('getData');
+ cy.visit('/');
+ cy.wait('@getData');
+ ```
+
+3. Increase viewport size:
+ ```javascript
+ cy.viewport(1920, 1080);
+ ```
+
+### Screenshot Attachment Failures
+
+**Issue:** Screenshots not attaching properly.
-For the legacy `cypress-cucumber-preprocessor`, steps are automatically captured. Add the following to your `support/e2e.js` file:
+**Solution:** Ensure screenshots folder is configured correctly:
```javascript
-import { enableCucumberSupport } from 'cypress-qase-reporter/cucumber';
+// cypress.config.js
+module.exports = defineConfig({
+ screenshotsFolder: 'cypress/screenshots',
+ reporterOptions: {
+ framework: {
+ cypress: {
+ screenshotsFolder: 'cypress/screenshots',
+ },
+ },
+ },
+});
+```
+
+### Import Path Issues
+
+**Issue:** Error: "Cannot find module 'cypress-qase-reporter/mocha'"
+
+**Solution:** Ensure you're using the correct import path:
-enableCucumberSupport();
+```javascript
+// Correct
+import { qase } from 'cypress-qase-reporter/mocha';
+
+// Incorrect
+import { qase } from 'cypress-qase-reporter';
+import { qase } from 'cypress-qase-reporter/cypress';
```
-No additional configuration is needed - all Gherkin steps will be automatically reported to Qase.
+### Multi-Reporter Configuration Conflicts
+
+**Issue:** Using cypress-multi-reporters causes conflicts.
+
+**Solution:** Configure Qase reporter directly in `cypress.config.js`:
+
+```javascript
+module.exports = defineConfig({
+ reporter: 'cypress-qase-reporter',
+ reporterOptions: {
+ mode: 'testops',
+ },
+});
+```
+
+---
+
+## Complete Examples
+
+### Full Test Example
+
+```javascript
+import { qase } from 'cypress-qase-reporter/mocha';
+
+describe('Complete Example', () => {
+ it(qase([1, 2], 'Comprehensive test with all features'), () => {
+ // Set metadata
+ qase.title('User can complete full registration flow');
+ qase.suite('Registration\tEnd-to-End');
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'e2e',
+ description: 'Tests complete user registration flow from start to finish',
+ preconditions: 'Application is running and database is accessible',
+ });
+ qase.parameters({
+ Browser: 'Chrome',
+ Environment: 'staging',
+ });
+
+ // Execute test with steps
+ qase.step('Navigate to registration page', () => {
+ cy.visit('/register');
+ qase.attach({
+ name: 'page-load.txt',
+ content: 'Page loaded successfully',
+ contentType: 'text/plain',
+ });
+ });
+
+ qase.step('Fill registration form', () => {
+ cy.get('#username').type('testuser');
+ cy.get('#email').type('test@example.com');
+ cy.get('#password').type('SecurePass123!');
+ });
+
+ qase.step('Submit form', () => {
+ cy.get('button[type="submit"]').click();
+ cy.get('.success-message').should('be.visible');
+ });
+
+ qase.step('Verify email confirmation', () => {
+ cy.get('.email-sent').should('contain.text', 'Verification email sent');
+ });
+ });
+});
+```
+
+### Example Project Structure
+
+```
+my-project/
+├── qase.config.json
+├── cypress.config.js
+├── cypress/
+│ ├── e2e/
+│ │ ├── auth.cy.js
+│ │ ├── checkout.cy.js
+│ │ └── ...
+│ ├── support/
+│ │ ├── commands.js
+│ │ └── e2e.js
+│ └── fixtures/
+│ └── ...
+└── package.json
+```
+
+---
+
+## See Also
+
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Cucumber Integration Guide](cucumber.md)
+- [Attachments Guide](ATTACHMENTS.md)
+- [Steps Guide](STEPS.md)
+- [Multi-Project Support](MULTI_PROJECT.md)
+- [Upgrade Guide](UPGRADE.md)
diff --git a/qase-jest/README.md b/qase-jest/README.md
index 1308e5d9..9c3524b9 100644
--- a/qase-jest/README.md
+++ b/qase-jest/README.md
@@ -1,42 +1,108 @@
-# Qase TestOps Jest reporter
+# [Qase TestOps](https://qase.io) Jest Reporter
-Qase Jest reporter sends test results and metadata to Qase.io.
-It can work in different test automation scenarios:
+[](https://www.apache.org/licenses/LICENSE-2.0)
+[](https://www.npmjs.com/package/jest-qase-reporter)
-* Create new test cases in Qase from existing autotests.
-* Report Jest test results to existing test cases in Qase.
+Qase Jest Reporter enables seamless integration between your Jest tests and [Qase TestOps](https://qase.io), providing automatic test result reporting, test case management, and comprehensive test analytics.
-Testing frameworks that use Jest as a test runner, such as Puppeteer, Appium, and Detox,
-can also be used with Jest reporter.
+## Features
-To install the latest version, run:
+- Link automated tests to Qase test cases by ID
+- Auto-create test cases from your test code
+- Report test results with rich metadata (fields, attachments, steps)
+- Support for parameterized tests
+- Multi-project reporting support
+- Flexible configuration (file, environment variables, Jest config)
-```shell
+## Installation
+
+```sh
npm install --save-dev jest-qase-reporter
```
-# Contents
+## Quick Start
+
+**1. Create `qase.config.json` in your project root:**
+
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "project": "YOUR_PROJECT_CODE",
+ "api": {
+ "token": "YOUR_API_TOKEN"
+ }
+ }
+}
+```
+
+**2. Add Qase ID to your test:**
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
-
-
+describe('User Authentication', () => {
+ test(qase(1, 'User can login with valid credentials'), () => {
+ expect(true).toBe(true);
+ });
+});
+```
-- [Getting started](#getting-started)
-- [Using Reporter](#using-reporter)
-- [Configuration](#configuration)
-- [Requirements](#requirements)
+**3. Run your tests:**
-
+```sh
+npx jest
+```
-## Getting started
+## Configuration
-To report your tests results to Qase, install `jest-qase-reporter`,
-and add a reporter config in the `jest.config.ts` file.
-A minimal configuration needs just two things:
+The reporter is configured via (in order of priority):
+
+1. **jest.config.js** (Jest-specific, highest priority)
+2. **Environment variables** (`QASE_*`)
+3. **Config file** (`qase.config.json`)
+
+### Minimal Configuration
+
+| Option | Environment Variable | Description |
+|--------|---------------------|-------------|
+| `mode` | `QASE_MODE` | Set to `testops` to enable reporting |
+| `testops.project` | `QASE_TESTOPS_PROJECT` | Your Qase project code |
+| `testops.api.token` | `QASE_TESTOPS_API_TOKEN` | Your Qase API token |
+
+### Example `qase.config.json`
+
+```json
+{
+ "mode": "testops",
+ "fallback": "report",
+ "testops": {
+ "project": "YOUR_PROJECT_CODE",
+ "api": {
+ "token": "YOUR_API_TOKEN"
+ },
+ "run": {
+ "title": "Jest Automated Run"
+ },
+ "batch": {
+ "size": 100
+ }
+ },
+ "report": {
+ "driver": "local",
+ "connection": {
+ "local": {
+ "path": "./build/qase-report",
+ "format": "json"
+ }
+ }
+ }
+}
+```
-* Qase project code, for example, in https://app.qase.io/project/DEMO the code is `DEMO`.
-* Qase API token, created on the [Apps page](https://app.qase.io/apps?app=jest-reporter).
+### Example `jest.config.js`
-```js
+```javascript
module.exports = {
reporters: [
'default',
@@ -46,9 +112,12 @@ module.exports = {
mode: 'testops',
testops: {
api: {
- token: 'api_token'
+ token: process.env.QASE_API_TOKEN,
+ },
+ project: 'YOUR_PROJECT_CODE',
+ run: {
+ complete: true,
},
- project: 'project_code',
},
},
],
@@ -56,152 +125,111 @@ module.exports = {
};
```
-Now, run the Jest tests as usual.
-Test results will be reported to a new test run in Qase.
-
-```console
-$ npx jest
-Determining test suites to run...
-...
-qase: Project DEMO exists
-qase: Using run 42 to publish test results
-...
-
-Ran all test suites.
-```
-
-## Using Reporter
-
-The Jest reporter has the ability to auto-generate test cases
-and suites from your test data.
+> **Full configuration reference:** See [qase-javascript-commons](../qase-javascript-commons/README.md) for all available options including logging, status mapping, execution plans, and more.
-But if necessary, you can independently register the ID of already
-existing test cases from TMS before the executing tests. For example:
+## Usage
-### Metadata
+### Link Tests with Test Cases
-- `qase.title` - set the title of the test case
-- `qase.fields` - set the fields of the test case
-- `qase.suite` - set the suite of the test case
-- `qase.comment` - set the comment of the test case
-- `qase.parameters` - set the parameters of the test case
-- `qase.groupParameters` - set the group parameters of the test case
-- `qase.ignore` - ignore the test case in Qase. The test will be executed, but the results will not be sent to Qase.
-- `qase.step` - create a step in the test case
-- `qase.attach` - attach a file to the test case
+Associate your tests with Qase test cases using test case IDs:
-```typescript
+```javascript
const { qase } = require('jest-qase-reporter/jest');
-describe('My First Test', () => {
- test(qase([1, 2], 'Several ids'), () => {
+describe('Test suite', () => {
+ // Single test case ID
+ test(qase(1, 'Test name'), () => {
expect(true).toBe(true);
});
- test(qase(3, 'Correct test'), () => {
- qase.title('Title');
+ // Multiple test case IDs
+ test(qase([1, 2, 3], 'Test covering multiple cases'), () => {
expect(true).toBe(true);
});
-
- test.skip(qase('4', 'Skipped test'), () => {
- expect(true).toBe(true);
- });
-
- test(qase(['5', '6'], 'Failed test'), () => {
- expect(true).toBe(false);
- });
});
```
-To run tests and create a test run, execute the command (for example from folder examples):
+### Add Metadata
-```bash
-QASE_MODE=testops npx jest --runInBand
-```
+Enhance your tests with additional information:
-or
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
-```bash
-npm test
+test(qase(1, 'Test with metadata'), () => {
+ qase.title('User can successfully login');
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'api',
+ });
+ qase.suite('Authentication / Login');
+
+ // Test logic
+ expect(true).toBe(true);
+});
```
-
-
-
+### Ignore Tests
-A test run will be performed and available at:
+Exclude specific tests from Qase reporting (test still runs, but results are not sent):
-```
-https://app.qase.io/run/QASE_PROJECT_CODE
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('This test runs but is not reported to Qase', () => {
+ qase.ignore();
+ expect(true).toBe(true);
+});
```
-### Multi-Project Support
+### Test Result Statuses
-Qase Jest Reporter supports sending test results to multiple Qase projects simultaneously. You can specify different test case IDs for each project using `qase.projects(mapping, name)`.
+| Jest Result | Qase Status |
+|-------------|-------------|
+| passed | passed |
+| failed | failed |
+| skipped | skipped |
-For detailed information, configuration, and examples, see the [Multi-Project Support Guide](docs/MULTI_PROJECT.md).
+> For more usage examples, see the [Usage Guide](docs/usage.md).
-## Configuration
+## Running Tests
-Reporter options (* - required):
+```bash
+# Run all tests with Qase reporting
+QASE_MODE=testops npx jest
-- `mode` - `testops`/`off` Enables reporter, default - `off`
-- `debug` - Enables debug logging, default - `false`
-- `environment` - To execute with the sending of the envinroment information
-- *`testops.api.token` - Token for API access, you can generate it [here](https://developers.qase.io/#authentication).
-- *`testops.project` - [Your project's code](https://help.qase.io/en/articles/9787250-how-do-i-find-my-project-code)
-- `testops.run.id` - Qase test run ID, used when the test run was created earlier using CLI or API call.
-- `testops.run.title` - Set custom Run name, when new run is created
-- `testops.run.description` - Set custom Run description, when new run is created
-- `testops.run.complete` - Whether the run should be completed
+# Run specific test file
+QASE_MODE=testops npx jest path/to/test.spec.js
-Example `jest.config.js` config:
+# Run tests matching pattern
+QASE_MODE=testops npx jest --testPathPattern="auth"
-```js
-module.exports = {
- reporters: [
- 'default',
- [
- 'jest-qase-reporter',
- {
- mode: 'testops',
- testops: {
- api: {
- token: 'api_key'
- },
- project: 'project_code',
- run: {
- complete: true,
- },
- },
- debug: true,
- },
- ],
- ],
- ...
-};
+# Run with custom test run title
+QASE_MODE=testops QASE_TESTOPS_RUN_TITLE="Nightly Regression" npx jest
```
-You can check example configuration with multiple reporters in [example project](../examples/jest/jest.config.js).
+## Requirements
-Supported ENV variables:
+- Node.js >= 14
+- Jest >= 27.0.0
-- `QASE_MODE` - Same as `mode`
-- `QASE_DEBUG` - Same as `debug`
-- `QASE_ENVIRONMENT` - Same as `environment`
-- `QASE_TESTOPS_API_TOKEN` - Same as `testops.api.token`
-- `QASE_TESTOPS_PROJECT` - Same as `testops.project`
-- `QASE_TESTOPS_RUN_ID` - Pass Run ID from ENV and override reporter option `testops.run.id`
-- `QASE_TESTOPS_RUN_TITLE` - Same as `testops.run.title`
-- `QASE_TESTOPS_RUN_DESCRIPTION` - Same as `testops.run.description`
+> **Note:** Testing frameworks that use Jest as a test runner, such as Puppeteer, Appium, and Detox, can also be used with Jest reporter.
-## Requirements
+## Documentation
+
+| Guide | Description |
+|-------|-------------|
+| [Usage Guide](docs/usage.md) | Complete usage reference with all methods and options |
+| [Attachments](docs/ATTACHMENTS.md) | Adding screenshots, logs, and files to test results |
+| [Steps](docs/STEPS.md) | Defining test steps for detailed reporting |
+| [Multi-Project Support](docs/MULTI_PROJECT.md) | Reporting to multiple Qase projects |
+| [Upgrade Guide](docs/UPGRADE.md) | Migration guide for breaking changes |
-We maintain the reporter on LTS versions of Node. You can find the current versions by following
-the [link](https://nodejs.org/en/about/releases/)
+## Examples
-`jest >= 28.0.0`
+See the [examples directory](../examples/) for complete working examples.
-
+## License
-[auth]: https://developers.qase.io/#authentication
+Apache License 2.0. See [LICENSE](LICENSE) for details.
diff --git a/qase-jest/docs/ATTACHMENTS.md b/qase-jest/docs/ATTACHMENTS.md
new file mode 100644
index 00000000..cfbce4e3
--- /dev/null
+++ b/qase-jest/docs/ATTACHMENTS.md
@@ -0,0 +1,311 @@
+# Attachments in Jest
+
+This guide covers how to attach files, screenshots, logs, and other content to your Qase test results.
+
+---
+
+## Overview
+
+Qase Jest Reporter supports attaching various types of content to test results:
+
+- **Files** — Attach files from the filesystem
+- **Screenshots** — Attach images captured during test execution
+- **Logs** — Attach text logs or console output
+- **Binary data** — Attach any binary content from memory
+
+Attachments can be added to:
+- **Test cases** — Visible in the overall test result
+- **Test steps** — Visible in specific step results
+
+---
+
+## Attaching Files
+
+### From File Path
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test with file attachment', () => {
+ qase.attach({ paths: '/path/to/file.txt' });
+
+ expect(true).toBe(true);
+});
+```
+
+### Multiple Files
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test with multiple attachments', () => {
+ qase.attach({
+ paths: [
+ '/path/to/file1.txt',
+ '/path/to/file2.log',
+ '/path/to/screenshot.png'
+ ]
+ });
+
+ expect(true).toBe(true);
+});
+```
+
+---
+
+## Attaching Content from Memory
+
+### Text Content
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test with text attachment', () => {
+ qase.attach({
+ name: 'log.txt',
+ content: 'Test execution log content',
+ contentType: 'text/plain',
+ });
+
+ expect(true).toBe(true);
+});
+```
+
+### Binary Content (Screenshots)
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+const fs = require('fs');
+
+test('Test with binary attachment', () => {
+ const screenshot = fs.readFileSync('/path/to/screenshot.png');
+
+ qase.attach({
+ name: 'screenshot.png',
+ content: screenshot,
+ contentType: 'image/png',
+ });
+
+ expect(true).toBe(true);
+});
+```
+
+### JSON Data
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test with JSON attachment', () => {
+ const data = {
+ userId: 123,
+ status: 'active',
+ timestamp: new Date().toISOString(),
+ };
+
+ qase.attach({
+ name: 'test-data.json',
+ content: JSON.stringify(data, null, 2),
+ contentType: 'application/json',
+ });
+
+ expect(true).toBe(true);
+});
+```
+
+---
+
+## Attaching to Steps
+
+Attach content to a specific test step:
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test with step attachments', async () => {
+ await qase.step('Initialize test data', async () => {
+ const testData = { user: 'testuser', role: 'admin' };
+
+ qase.attach({
+ name: 'init-data.json',
+ content: JSON.stringify(testData, null, 2),
+ contentType: 'application/json',
+ });
+ });
+
+ await qase.step('Execute test', async () => {
+ qase.attach({
+ name: 'execution-log.txt',
+ content: 'Test execution completed successfully',
+ contentType: 'text/plain',
+ });
+
+ expect(true).toBe(true);
+ });
+});
+```
+
+---
+
+## Method Reference
+
+### `qase.attach()`
+
+Attach content to the test case.
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `paths` | `string` or `string[]` | No* | Path(s) to file(s) to attach |
+| `content` | `string` or `Buffer` | No* | Content to attach |
+| `name` | `string` | No | Custom filename (auto-detected from path) |
+| `contentType` | `string` | No | MIME type (auto-detected from extension) |
+
+\* Either `paths` or `content` must be provided, but not both.
+
+**CommonJS:**
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+qase.attach({ paths: '/path/to/file.txt' });
+```
+
+**ES Modules:**
+```javascript
+import { qase } from 'jest-qase-reporter/jest';
+
+qase.attach({ paths: '/path/to/file.txt' });
+```
+
+---
+
+## MIME Types
+
+Common MIME types are auto-detected based on file extension:
+
+| Extension | MIME Type |
+|-----------|-----------|
+| `.png` | `image/png` |
+| `.jpg`, `.jpeg` | `image/jpeg` |
+| `.gif` | `image/gif` |
+| `.svg` | `image/svg+xml` |
+| `.txt` | `text/plain` |
+| `.log` | `text/plain` |
+| `.json` | `application/json` |
+| `.xml` | `application/xml` |
+| `.html` | `text/html` |
+| `.csv` | `text/csv` |
+| `.pdf` | `application/pdf` |
+| `.zip` | `application/zip` |
+
+For other file types, specify `contentType` explicitly.
+
+---
+
+## Common Use Cases
+
+### Saving Test Artifacts
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test that generates artifacts', async () => {
+ // Generate test data
+ const testResults = {
+ passed: 10,
+ failed: 2,
+ skipped: 1,
+ };
+
+ qase.attach({
+ name: 'test-results.json',
+ content: JSON.stringify(testResults, null, 2),
+ contentType: 'application/json',
+ });
+
+ expect(testResults.passed).toBeGreaterThan(0);
+});
+```
+
+### API Response Logs
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('API request with response logging', async () => {
+ const response = await fetch('https://api.example.com/users');
+ const data = await response.json();
+
+ qase.attach({
+ name: 'api-response.json',
+ content: JSON.stringify(data, null, 2),
+ contentType: 'application/json',
+ });
+
+ expect(response.status).toBe(200);
+});
+```
+
+### Console Logs
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test with captured console output', () => {
+ const logs = [];
+ const originalLog = console.log;
+
+ console.log = (...args) => {
+ logs.push(args.join(' '));
+ originalLog(...args);
+ };
+
+ console.log('Test started');
+ console.log('Processing data...');
+ console.log('Test completed');
+
+ qase.attach({
+ name: 'console-log.txt',
+ content: logs.join('\n'),
+ contentType: 'text/plain',
+ });
+
+ console.log = originalLog;
+
+ expect(logs.length).toBeGreaterThan(0);
+});
+```
+
+---
+
+## Troubleshooting
+
+### Attachments Not Appearing
+
+1. Verify the file path exists and is readable
+2. Check file permissions
+3. Enable debug logging to see upload status:
+ ```json
+ {
+ "debug": true
+ }
+ ```
+
+### Large Files
+
+Large attachments may slow down test execution. Consider:
+- Compressing logs before attaching
+- Using selective logging (e.g., only on failure)
+- Setting reasonable size limits
+
+### Binary Data Issues
+
+When attaching binary data, always specify:
+- `name` with appropriate extension
+- `contentType` if extension doesn't match content type
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Steps Guide](STEPS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-jest/docs/MULTI_PROJECT.md b/qase-jest/docs/MULTI_PROJECT.md
index cc1e3071..8270c2fe 100644
--- a/qase-jest/docs/MULTI_PROJECT.md
+++ b/qase-jest/docs/MULTI_PROJECT.md
@@ -6,6 +6,8 @@ Qase Jest Reporter supports sending test results to multiple Qase projects simul
* Different projects track the same functionality with different test case IDs
* You want to maintain separate test runs for different environments or teams
+---
+
## Configuration
For detailed configuration options, refer to the [qase-javascript-commons README](../../qase-javascript-commons/README.md#multi-project-support).
@@ -14,40 +16,156 @@ For detailed configuration options, refer to the [qase-javascript-commons README
Set `mode` to `testops_multi` in your Jest reporter options (e.g. in `jest.config.js` or `qase.config.json`) and add the `testops_multi` section with `default_project` and `projects`.
+**Example configuration:**
+
+```json
+{
+ "mode": "testops_multi",
+ "testops_multi": {
+ "default_project": "PROJ1",
+ "projects": [
+ {
+ "code": "PROJ1",
+ "api": {
+ "token": "your_api_token_for_proj1"
+ }
+ },
+ {
+ "code": "PROJ2",
+ "api": {
+ "token": "your_api_token_for_proj2"
+ }
+ }
+ ]
+ }
+}
+```
+
+---
+
## Using `qase.projects(mapping, name)`
Use `qase.projects(mapping, name)` to set the test title with multi-project markers. The first argument is the mapping (project code → array of case IDs); the second is the test name. Use the returned string as the test name:
```javascript
-const { qase } = require('jest-qase-reporter');
+const { qase } = require('jest-qase-reporter/jest');
// Single project with single ID
-test(qase(100, 'login flow'), () => { ... });
+test(qase(100, 'login flow'), () => {
+ expect(true).toBe(true);
+});
// Multi-project: one test, multiple projects
-test(qase.projects({ PROJ1: [100], PROJ2: [200] }, 'login flow'), () => { ... });
+test(qase.projects({ PROJ1: [100], PROJ2: [200] }, 'login flow'), () => {
+ expect(true).toBe(true);
+});
// Multiple IDs per project
-test(qase.projects({ PROJ1: [10, 11], PROJ2: [20] }, 'checkout'), () => { ... });
+test(qase.projects({ PROJ1: [10, 11], PROJ2: [20] }, 'checkout'), () => {
+ expect(true).toBe(true);
+});
```
+**Key points:**
+
+- Single project with single ID: `test(qase(100, 'test name'), () => { ... })`
+- Multi-project: `test(qase.projects({ PROJ1: [100], PROJ2: [200] }, 'test name'), () => { ... })`
+- Multiple IDs per project: `test(qase.projects({ PROJ1: [10, 11], PROJ2: [20] }, 'test name'), () => { ... })`
+
Project codes (e.g. `PROJ1`, `PROJ2`) must match `testops_multi.projects[].code` in your config.
+---
+
## Tests Without Project Mapping
-Tests that do not use `qase.projects()` and have no `(Qase PROJ: ids)` in the title are sent to the `default_project`. If they use `qase(id, name)` (single-project), that ID is used for the default project.
+Tests that do not use `qase.projects()` and have no `(Qase PROJ: ids)` in the title are sent to the `default_project`. If they use `qase(id, name)` (single-project syntax), that ID is used for the default project.
+
+---
## Important Notes
-1. **Project codes must match**: Codes in `qase.projects({ PROJ1: [1], ... })` must match `testops_multi.projects[].code`.
+1. **Project codes must match**: Codes in `qase.projects({ PROJ1: [1], ... })` must match `testops_multi.projects[].code` in config.
2. **Mode**: Set `mode` to `testops_multi` in reporter config.
3. **Title format**: The helper produces a title like `Name (Qase PROJ1: 1,2) (Qase PROJ2: 3)` so the reporter can parse the mapping.
+4. **API tokens**: Each project in `testops_multi.projects[]` can have its own API token for separate authentication.
+
+---
## Examples
See the [multi-project Jest example](../../examples/multiProject/jest/) for a complete runnable setup.
+### Complete Example
+
+Here's a complete Jest test file showing multi-project usage:
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+describe('Multi-project test suite', () => {
+ // Test reported to two projects
+ test(qase.projects({ PROJ1: [1], PROJ2: [2] }, 'User can login successfully'), () => {
+ const username = 'testuser';
+ const password = 'password123';
+
+ expect(username).toBeDefined();
+ expect(password).toBeDefined();
+ });
+
+ // Test with multiple case IDs per project
+ test(qase.projects({ PROJ1: [10, 11], PROJ2: [20] }, 'Checkout process works'), () => {
+ const cart = { items: 2, total: 99.99 };
+
+ expect(cart.items).toBeGreaterThan(0);
+ expect(cart.total).toBeGreaterThan(0);
+ });
+
+ // Combining multi-project with other Qase methods
+ test(qase.projects({ PROJ1: [100], PROJ2: [200] }, 'User registration'), () => {
+ qase.title('Complete user registration flow');
+ qase.fields({ severity: 'critical', priority: 'high' });
+
+ expect(true).toBe(true);
+ });
+
+ // Single-project test (uses default_project)
+ test(qase(50, 'Test reported to default project'), () => {
+ expect(1 + 1).toBe(2);
+ });
+
+ // Test without Qase metadata (goes to default_project without case ID)
+ test('Regular test without Qase tracking', () => {
+ expect(true).toBe(true);
+ });
+});
+```
+
+---
+
## Troubleshooting
-* Verify `mode` is `testops_multi` and project codes in `qase.projects()` match the config.
-* Ensure the first argument to `test()` is the string returned by `qase.projects(mapping, name)` (or an equivalent title with markers).
+### Results Not Appearing in All Projects
+
+* Verify `mode` is `testops_multi` (not `TestOps`) in reporter config
+* Check that project codes in `qase.projects()` match config codes exactly (case-sensitive)
+* Ensure each project has a valid API token with write permissions
+
+### Wrong Test Cases Linked
+
+* Verify the mapping object has correct project codes as keys
+* Check that test case IDs exist in the respective projects
+* Enable debug logging to see how the reporter parses multi-project markers
+
+### Default Project Not Working
+
+* Ensure `default_project` is set in `testops_multi` config
+* Verify the default project code matches one of the projects in the `projects` array
+* Tests without `qase.projects()` will only report to the default project
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Examples](../../examples/multiProject/jest/)
diff --git a/qase-jest/docs/STEPS.md b/qase-jest/docs/STEPS.md
new file mode 100644
index 00000000..b710bcb7
--- /dev/null
+++ b/qase-jest/docs/STEPS.md
@@ -0,0 +1,371 @@
+# Test Steps in Jest
+
+This guide covers how to define and report test steps for detailed execution tracking in Qase.
+
+---
+
+## Overview
+
+Test steps provide granular visibility into test execution. Each step is reported separately, showing:
+
+- Step name and description
+- Step status (passed/failed)
+- Step duration
+- Attachments (if any)
+- Error details (on failure)
+
+---
+
+## Defining Steps
+
+### Using Async Function
+
+Define steps as async functions with callbacks:
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test with multiple steps', async () => {
+ await qase.step('Initialize the environment', async () => {
+ // Set up test environment
+ });
+
+ await qase.step('Test Core Functionality of the app', async () => {
+ // Exercise core functionality
+ });
+
+ await qase.step('Verify Expected Behavior of the app', async () => {
+ // Assert expected behavior
+ expect(result).toBe(expected);
+ });
+});
+```
+
+### Step Parameters
+
+Steps can include parameters for dynamic naming:
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test with dynamic step names', async () => {
+ const username = 'john@example.com';
+
+ await qase.step(`Login as user ${username}`, async () => {
+ await login(username, 'password');
+ });
+
+ await qase.step(`Verify ${username} profile loaded`, async () => {
+ expect(getUsername()).toBe(username);
+ });
+});
+```
+
+---
+
+## Nested Steps
+
+Create hierarchical step structures:
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test with nested steps', async () => {
+ await qase.step('Complete user registration', async () => {
+ await qase.step('Fill registration form', async () => {
+ // Fill form fields
+ });
+
+ await qase.step('Submit registration', async () => {
+ // Click submit button
+ });
+ });
+
+ await qase.step('Verify registration success', async () => {
+ expect(isRegistered()).toBe(true);
+ });
+});
+```
+
+---
+
+## Steps with Expected Result and Data
+
+Define expected results and data for steps:
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test with expected results', async () => {
+ await qase.step(
+ 'Click button',
+ async () => {
+ // Click action
+ },
+ 'Button should be clicked',
+ 'Button data'
+ );
+
+ await qase.step(
+ 'Fill form',
+ async () => {
+ // Form filling action
+ },
+ 'Form should be filled',
+ 'Form input data'
+ );
+
+ await qase.step(
+ 'Submit form',
+ async () => {
+ // Submit action
+ },
+ 'Form should be submitted',
+ 'Form submission data'
+ );
+});
+```
+
+**Signature:**
+```typescript
+await qase.step(
+ name: string,
+ callback: () => Promise | void,
+ expectedResult?: string,
+ data?: string
+): Promise
+```
+
+---
+
+## Steps with Attachments
+
+Attach content to a specific step:
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test with step attachments', async () => {
+ await qase.step('Capture application state', async () => {
+ const state = JSON.stringify({ user: 'john', status: 'active' });
+
+ qase.attach({
+ name: 'app-state.json',
+ content: state,
+ contentType: 'application/json',
+ });
+ });
+
+ await qase.step('Verify state', async () => {
+ // Assertions
+ });
+});
+```
+
+---
+
+## Step Status
+
+Steps automatically inherit status from execution:
+
+| Execution | Step Status |
+|-----------|-------------|
+| Completes normally | Passed |
+| Throws Error/AssertionError | Failed |
+| Other exception | Invalid |
+
+---
+
+## Best Practices
+
+### Keep Steps Atomic
+
+Each step should represent a single action:
+
+```javascript
+// Good: One action per step
+await qase.step('Navigate to login page', async () => {
+ await page.goto('/login');
+});
+
+await qase.step('Enter username', async () => {
+ await page.fill('#username', 'user');
+});
+
+// Avoid: Multiple actions in one step
+await qase.step('Login to application', async () => { // Too broad
+ await page.goto('/login');
+ await page.fill('#username', 'user');
+ await page.fill('#password', 'pass');
+ await page.click('#submit');
+});
+```
+
+### Use Descriptive Names
+
+```javascript
+// Good: Clear action description
+await qase.step('Verify user is redirected to dashboard', async () => {
+ expect(window.location.pathname).toBe('/dashboard');
+});
+
+// Avoid: Vague names
+await qase.step('Check page', async () => {
+ expect(window.location.pathname).toBe('/dashboard');
+});
+```
+
+### Include Context in Step Names
+
+```javascript
+// Good: Include relevant context
+await qase.step(`Add product '${productName}' to cart`, async () => {
+ await addToCart(productName);
+});
+
+// Better than generic:
+await qase.step('Add product', async () => {
+ await addToCart(productName);
+});
+```
+
+---
+
+## Common Patterns
+
+### Page Object Steps
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+class LoginPage {
+ async login(username, password) {
+ await qase.step(`Enter username: ${username}`, async () => {
+ // Fill username field
+ });
+
+ await qase.step('Enter password', async () => {
+ // Fill password field (don't log password)
+ });
+
+ await qase.step('Click login button', async () => {
+ // Click submit
+ });
+ }
+}
+
+test('User can login', async () => {
+ const loginPage = new LoginPage();
+ await loginPage.login('user@example.com', 'password');
+});
+```
+
+### API Testing Steps
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('API returns correct user data', async () => {
+ let response;
+
+ await qase.step('Send GET request to /api/users/1', async () => {
+ response = await fetch('https://api.example.com/users/1');
+ });
+
+ await qase.step('Verify response status is 200', async () => {
+ expect(response.status).toBe(200);
+ });
+
+ await qase.step('Verify response contains user data', async () => {
+ const data = await response.json();
+ expect(data.id).toBe(1);
+ expect(data.name).toBeDefined();
+ });
+});
+```
+
+### Setup/Teardown Steps
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+describe('User tests', () => {
+ beforeEach(async () => {
+ await qase.step('Setup: Create test user', async () => {
+ // Create user in database
+ });
+
+ await qase.step('Setup: Initialize session', async () => {
+ // Set up user session
+ });
+ });
+
+ afterEach(async () => {
+ await qase.step('Cleanup: Delete test user', async () => {
+ // Remove user from database
+ });
+ });
+
+ test('Test user operations', async () => {
+ await qase.step('Perform user action', async () => {
+ // Test logic
+ });
+ });
+});
+```
+
+---
+
+## Troubleshooting
+
+### Steps Not Appearing
+
+1. Verify the step function is properly imported from `jest-qase-reporter/jest`
+2. Check that steps are executed within a test context
+3. Enable debug logging to trace step recording
+4. **Ensure you're using `await` with async step callbacks** - Missing `await` is the most common issue
+
+```javascript
+// Incorrect: Missing await
+qase.step('Step name', async () => { // Step won't be recorded properly
+ // Logic
+});
+
+// Correct: Using await
+await qase.step('Step name', async () => {
+ // Logic
+});
+```
+
+### Nested Steps Flattened
+
+Ensure you're using the async callbacks correctly for nesting:
+
+```javascript
+// Correct: Nested callbacks
+await qase.step('Parent step', async () => {
+ await qase.step('Child step', async () => {
+ // Child step logic
+ });
+});
+
+// Incorrect: Sequential, not nested
+await qase.step('Step 1', async () => {
+ // Step 1 logic
+});
+await qase.step('Step 2', async () => { // Not nested under Step 1
+ // Step 2 logic
+});
+```
+
+### Step Duration Shows 0
+
+Steps need measurable execution time. Very fast steps may show 0ms duration.
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Attachments Guide](ATTACHMENTS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-jest/docs/UPGRADE.md b/qase-jest/docs/UPGRADE.md
new file mode 100644
index 00000000..9d1bb106
--- /dev/null
+++ b/qase-jest/docs/UPGRADE.md
@@ -0,0 +1,260 @@
+# Upgrade Guide: Jest Reporter
+
+This guide covers migration steps between major versions of the Qase Jest Reporter.
+
+---
+
+## Version History
+
+| Version | Release Date | Node.js Support | Key Changes |
+|---------|--------------|-----------------|-------------|
+| 2.2.0 | January 2026 | >= 14 | Current stable release with improved reporter functionality |
+| 2.1.0 | December 2025 | >= 14 | Enhanced metadata handling and multi-project support |
+| 2.0.0 | August 2025 | >= 14 | Complete rewrite with new architecture and API |
+
+---
+
+## Upgrading to 2.x
+
+### Breaking Changes
+
+The jest-qase-reporter started with the v2.x architecture, leveraging the unified qase-javascript-commons library for consistent reporting across all test frameworks. If you are using v2.x, you are already on the latest architecture.
+
+**No migration from a previous major version is required for jest-qase-reporter.**
+
+### Current Version Features
+
+Version 2.2.0 includes:
+
+- Full support for Qase TestOps API with batch result upload
+- Test case linking via wrapper function pattern: `test(qase(id, 'name'), callback)`
+- Rich metadata support: titles, fields, suites, parameters, comments
+- Nested test steps with `qase.step()`
+- File and content-based attachments with `qase.attach()`
+- Multi-project support for reporting to multiple Qase projects
+- Flexible configuration via `qase.config.json` or reporter options
+
+---
+
+## Configuration
+
+### Current Format (v2.x)
+
+Configuration uses the modern qase-javascript-commons format:
+
+```javascript
+// jest.config.js
+module.exports = {
+ reporters: [
+ 'default',
+ [
+ 'jest-qase-reporter',
+ {
+ mode: 'testops',
+ debug: false,
+ testops: {
+ api: {
+ token: process.env.QASE_API_TOKEN,
+ },
+ project: 'DEMO',
+ run: {
+ title: 'Jest Automated Run',
+ description: 'Test run from CI/CD pipeline',
+ complete: true,
+ },
+ batch: {
+ size: 100,
+ },
+ },
+ },
+ ],
+ ],
+};
+```
+
+**Alternative: qase.config.json**
+
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "api": {
+ "token": "api_token_here"
+ },
+ "project": "DEMO",
+ "run": {
+ "complete": true
+ }
+ }
+}
+```
+
+---
+
+## Import Pattern
+
+### Current Import (v2.x)
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+```
+
+**ES Modules:**
+
+```javascript
+import { qase } from 'jest-qase-reporter/jest';
+```
+
+**Note:** The `/jest` subpath is required to access the `Qase` helper function for test annotations.
+
+---
+
+## API Reference
+
+### Test Case ID Linking
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+describe('User Authentication', () => {
+ // Single test case ID
+ test(qase(1, 'User can login'), () => {
+ expect(true).toBe(true);
+ });
+
+ // Multiple test case IDs
+ test(qase([1, 2], 'Multiple IDs'), () => {
+ expect(true).toBe(true);
+ });
+});
+```
+
+### Metadata Methods
+
+```javascript
+test('Test with metadata', () => {
+ qase.title('Custom test title');
+ qase.fields({ severity: 'critical', priority: 'high' });
+ qase.suite('Authentication / Login');
+ qase.comment('This test covers the main login flow');
+ qase.parameters({ browser: 'Chrome', environment: 'staging' });
+
+ expect(true).toBe(true);
+});
+```
+
+### Steps
+
+```javascript
+test('Test with steps', async () => {
+ await qase.step('Initialize environment', async () => {
+ // Setup code
+ });
+
+ await qase.step('Perform action', async () => {
+ // Test logic
+ });
+
+ await qase.step('Verify result', async () => {
+ // Assertions
+ });
+});
+```
+
+### Attachments
+
+```javascript
+// Path-based attachment
+qase.attach({ paths: '/path/to/log.txt' });
+
+// Content-based attachment
+qase.attach({
+ name: 'test-log.txt',
+ content: 'Test execution log content',
+ contentType: 'text/plain',
+});
+```
+
+---
+
+## Compatibility Notes
+
+### Node.js Version Support
+
+- **Current (2.2.0):** Node.js >= 14
+
+### Jest Version Support
+
+- **Current (2.2.0):** Jest >= 28.0.0
+- Tested with Jest 29.x
+
+### Framework Compatibility
+
+- CommonJS and ES Modules supported
+- TypeScript support with full type definitions
+- Works with ts-jest for TypeScript projects
+
+---
+
+## Troubleshooting
+
+### Common Issues
+
+#### Issue: Module not found after installation
+
+**Solution:** Ensure you're importing from the correct subpath:
+
+```javascript
+// Correct
+const { qase } = require('jest-qase-reporter/jest');
+
+// Incorrect - missing /jest subpath
+const { qase } = require('jest-qase-reporter');
+```
+
+#### Issue: Reporter not running
+
+**Solution:** Verify that `QASE_MODE=testops` is set when running tests:
+
+```bash
+QASE_MODE=testops npx jest
+```
+
+Or configure `mode: 'TestOps'` in your reporter options.
+
+#### Issue: Configuration not recognized
+
+**Solution:** Check that your configuration follows the correct structure. The `testops` object should contain `api.token` and `project` fields:
+
+```javascript
+{
+ mode: 'testops',
+ testops: {
+ api: { token: 'your_token' },
+ project: 'YOUR_PROJECT_CODE',
+ }
+}
+```
+
+---
+
+## Getting Help
+
+If you encounter issues:
+
+1. Check the [GitHub Issues](https://github.com/qase-tms/qase-javascript/issues)
+2. Review the CHANGELOG
+3. Open a new issue with:
+ - Current version (2.2.0)
+ - Error messages
+ - Configuration file (without sensitive data)
+ - Steps to reproduce
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Multi-Project Support](MULTI_PROJECT.md)
+- CHANGELOG
diff --git a/qase-jest/docs/usage.md b/qase-jest/docs/usage.md
index 7e2c15ac..8ba0475b 100644
--- a/qase-jest/docs/usage.md
+++ b/qase-jest/docs/usage.md
@@ -1,227 +1,900 @@
-# Qase Syntax
+# Qase Integration in Jest
-> [**Click here**](../../examples/jest/test) to view Example tests for the following syntax.
+This guide provides comprehensive instructions for integrating Qase with Jest.
-Here is the complete list of syntax options available for the reporter:
-- [Qase Id](#qase-id)
-- [Qase Title](#qase-title)
-- [Steps](#steps)
-- [Fields](#fields)
-- [Suite](#suite)
-- [Parameters](#parameters)
-- [Comment](#comment)
-- [Attach](#attach)
-- [Ignore](#ignore)
+> **Configuration:** For complete configuration reference including all available options, environment variables, and examples, see the [qase-javascript-commons README](../../qase-javascript-commons/README.md).
-If you do not use any Qase syntax, the reporter uses the title from the `describe` and `test` functions as the Suite and Test case title respectively, when publishing results.
+---
+
+## Table of Contents
+
+- [Adding QaseID](#adding-qaseid)
+- [Adding Title](#adding-title)
+- [Adding Fields](#adding-fields)
+- [Adding Suite](#adding-suite)
+- [Ignoring Tests](#ignoring-tests)
+- [Muting Tests](#muting-tests)
+- [Working with Attachments](#working-with-attachments)
+- [Working with Steps](#working-with-steps)
+- [Working with Parameters](#working-with-parameters)
+- [Multi-Project Support](#multi-project-support)
+- [Running Tests](#running-tests)
+- [Integration Patterns](#integration-patterns)
+- [Common Use Cases](#common-use-cases)
+- [Troubleshooting](#troubleshooting)
+- [Complete Examples](#complete-examples)
+
+- [See Also](#see-also)
+---
+
+## Adding QaseID
+
+Link your automated tests to existing test cases in Qase by specifying the test case ID.
+
+### Single ID
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+describe('Authentication', () => {
+ test(qase(1, 'User can login'), () => {
+ expect(true).toBe(true);
+ });
+});
+```
+
+### Multiple IDs
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+describe('Authentication', () => {
+ test(qase([1, 2, 3], 'Test covering multiple scenarios'), () => {
+ // This test result will be reported to test cases 1, 2, and 3
+ expect(true).toBe(true);
+ });
+});
+```
+
+### Multi-Project Support
-
+To send test results to multiple Qase projects simultaneously, see the [Multi-Project Support Guide](MULTI_PROJECT.md).
-### Import Statement
---
-Add the following statement at the beginning of your spec file, before any tests.
+
+## Adding Title
+
+Set a custom title for the test case (overrides auto-generated title):
```javascript
-const { qase } = require("jest-qase-reporter/jest");
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test method name', () => {
+ qase.title('User can successfully login with valid credentials');
+ expect(true).toBe(true);
+});
```
-
-### Qase ID
---
-You can link one or more Qase Ids to a test.
+## Adding Fields
+
+Add metadata to your test cases using fields. Both system and custom fields are supported.
+
+### System Fields
+
+| Field | Description | Example Values |
+|-------|-------------|----------------|
+| `description` | Test case description | Any text |
+| `preconditions` | Test preconditions | Any text (supports Markdown) |
+| `postconditions` | Test postconditions | Any text |
+| `severity` | Test severity | `blocker`, `critical`, `major`, `normal`, `minor`, `trivial` |
+| `priority` | Test priority | `high`, `medium`, `low` |
+| `layer` | Test layer | `e2e`, `api`, `unit` |
+
+### Example
```javascript
-test(qase(1, "A test with Qase Id"), () => {
- ..
-
-test(qase(['2', '3'], "A test with multiple Qase Ids"), () => {
- ..
+const { qase } = require('jest-qase-reporter/jest');
+
+test(qase(1, 'Login test'), () => {
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'api',
+ description: 'Verify user authentication flow',
+ preconditions: 'User must be registered in the system',
+ postconditions: 'User session is created',
+ });
+
+ // Test logic
+ expect(true).toBe(true);
+});
```
-
+---
+
+## Adding Suite
-### Qase Title
----
+Organize tests into suites and sub-suites:
-The `qase.title()` method is used to set the title of a test case, both when creating a new test case from the result, and when updating the title of an existing test case - *if used with `qase.id()`.*
+### Simple Suite
```javascript
-test("This won't appear in Qase", () => {
- qase.title("This text will be the title of the test, in Qase");
- // Test logic here
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Login test', () => {
+ qase.suite('Authentication');
+ expect(true).toBe(true);
});
```
-If you don’t explicitly set a title using this method, the title specified in the `test(..)` function will be used for creating new test cases. However, if this method is defined, it always takes precedence and overrides the title from the `test(..)` function.
+### Nested Suites
-
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
-### Steps
----
+test('Login test', () => {
+ qase.suite('Authentication\tLogin\tPositive Cases');
+ expect(true).toBe(true);
+});
+```
+
+---
-The reporter uses the title from the `test.step` function as the step title. By providing clear and descriptive step names, you make it easier to understand the test’s flow when reviewing the test case.
+## Ignoring Tests
-Additionally, these steps get their own result in the Qase Test run, offering a well-organized summary of the test flow. This helps quickly identify the cause of any failures.
+Exclude a test from Qase reporting. The test still executes, but results are not sent to Qase:
```javascript
-const { qase } = require("jest-qase-reporter/jest");
+const { qase } = require('jest-qase-reporter/jest');
-test('A Test case with steps, updated from code', async () => {
- await qase.step('Initialize the environment', async () => {
- // Set up test environment
- });
- await qase.step('Test Core Functionality of the app', async () => {
- // Exercise core functionality
+test('This test runs but is not reported', () => {
+ qase.ignore();
+ expect(true).toBe(true);
+});
+```
+
+---
+
+## Muting Tests
+
+Mark a test as muted. Muted tests are reported but do not affect the test run status:
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test(qase(1, 'Known failing test'), () => {
+ qase.mute();
+ expect(false).toBe(true); // This failure won't affect the run status
+});
+```
+
+---
+
+## Working with Attachments
+
+Attach files, screenshots, logs, and other content to your test results.
+
+### Attach File from Path
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test with file attachment', () => {
+ // Single file
+ qase.attach({ paths: '/path/to/screenshot.png' });
+
+ // Multiple files
+ qase.attach({
+ paths: ['/path/to/log.txt', '/path/to/screenshot.png']
});
- await qase.step('Verify Expected Behavior of the app', async () => {
- // Assert expected behavior
+ expect(true).toBe(true);
+});
+```
+
+### Attach Content from Code
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test with content attachment', () => {
+ qase.attach({
+ name: 'execution-log.txt',
+ content: 'Test execution details...',
+ contentType: 'text/plain',
});
+
+ expect(true).toBe(true);
});
```
-#### Steps with Expected Result and Data
+### Attach Binary Content
```javascript
-const { qase } = require("jest-qase-reporter/jest");
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test with screenshot', () => {
+ const screenshotBuffer = Buffer.from('...', 'base64');
-test('A Test case with steps including expected results and data', async () => {
- await qase.step('Click button', async () => {
- // Click action
- }, 'Button should be clicked', 'Button data');
-
- await qase.step('Fill form', async () => {
- // Form filling action
- }, 'Form should be filled', 'Form input data');
+ qase.attach({
+ name: 'screenshot.png',
+ content: screenshotBuffer,
+ contentType: 'image/png',
+ });
+
+ expect(true).toBe(true);
});
```
-
-### Fields
+### Supported MIME Types
+
+Common MIME types are auto-detected. You can also specify explicitly:
+
+| Extension | MIME Type |
+|-----------|-----------|
+| `.png` | `image/png` |
+| `.jpg`, `.jpeg` | `image/jpeg` |
+| `.gif` | `image/gif` |
+| `.txt` | `text/plain` |
+| `.json` | `application/json` |
+| `.xml` | `application/xml` |
+| `.html` | `text/html` |
+| `.pdf` | `application/pdf` |
+
+> For more details, see [Attachments Guide](ATTACHMENTS.md).
+
---
-You can define the `description`, `pre-conditions`, `post-conditions`, and fields such as `severity`, `priority`, and `layer` using this method, which enables you to specify and maintain the context of the case directly within your code.
+## Working with Steps
+
+Define test steps for detailed reporting in Qase.
+
+### Using Async Function
```javascript
-test('Maintain your test meta-data from code', async () => {
- await qase.fields({
- severity: 'high',
- priority: 'medium',
- layer: 'api',
- precondition: 'add your precondition',
- postcondition: 'add your postcondition',
- description: `Code it quick, fix it slow,
- Tech debt grows where shortcuts go,
- Refactor later? Ha! We know.`
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test with steps', async () => {
+ await qase.step('Initialize environment', async () => {
+ // Setup code
+ });
+
+ await qase.step('Execute main test flow', async () => {
+ // Test logic
+ });
+
+ await qase.step('Verify results', async () => {
+ // Assertions
+ expect(true).toBe(true);
});
- // test logic here
});
```
-
+### Nested Steps
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
-### Suite
----
+test('Test with nested steps', async () => {
+ await qase.step('Parent step', async () => {
+ await qase.step('Child step 1', async () => {
+ // Nested logic
+ });
+
+ await qase.step('Child step 2', async () => {
+ // More nested logic
+ });
+ });
+});
+```
-You can use this method to nest the resulting test cases in a particular suite. There's something to note here – suites, unlike test cases, are not identified uniquely by the Reporter. Therefore, when defining an existing suite - the title of the suite is used for matching.
+### Steps with Expected Result
-```js
-test("Test with a defined suite", () => {
- qase.suite("Suite defined with qase.suite()");
- /*
- * Or, nest multiple levels of suites.
- * `\t` is used for dividing each suite name.
- */
-
-test("Test with a nested suite", () => {
- qase.suite("Application\tAuthentication\tLogin\tEdge_case");
- // test logic here
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test('Test with expected results', async () => {
+ await qase.step(
+ 'Click login button',
+ async () => {
+ // Click action
+ },
+ 'Button should be clicked',
+ 'Button data'
+ );
});
```
-
-### Parameters
+> For more details, see [Steps Guide](STEPS.md).
+
---
-Parameters are a great way to make your tests more dynamic, reusable, and data-driven. By defining parameters in this method, you can ensure only one test case with all the parameters is created in your Qase project, avoiding duplication.
+## Working with Parameters
+
+Report parameterized test data to Qase.
+
+### Basic Parameterized Test
```javascript
-const testCases = [
- { browser: "Chromium", username: "@alice", password: "123" },
- { browser: "Firefox", username: "@bob", password: "456" },
- { browser: "Webkit", username: "@charlie", password: "789" },
-];
+const { qase } = require('jest-qase-reporter/jest');
-testCases.forEach(({ browser, username, password, }) => {
- test(`Test login with ${browser}`, async () => {
- qase.title("Verify if page loads on all browsers");
+const browsers = ['Chrome', 'Firefox', 'Safari'];
- qase.parameters({ Browser: browser }); // Single parameter
- // test logic
+browsers.forEach((browser) => {
+ test(`Test on ${browser}`, () => {
+ qase.title('Browser compatibility test');
+ qase.parameters({ Browser: browser });
-testCases.forEach(({ username, password }) => {
- test(`Test login with ${username} using qase.groupParameters`, () => {
- qase.title("Verify if user is able to login with their username.");
+ // Test logic
+ expect(true).toBe(true);
+ });
+});
+```
- qase.groupParameters({ // Group parameters
+### Group Parameters
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+const testData = [
+ { username: 'user1', password: 'pass1' },
+ { username: 'user2', password: 'pass2' },
+];
+
+testData.forEach(({ username, password }) => {
+ test(`Login with ${username}`, () => {
+ qase.title('User login test');
+ qase.groupParameters({
Username: username,
Password: password,
});
- // test logic
+
+ // Test logic
+ expect(true).toBe(true);
+ });
+});
+```
+
+### Using Jest test.each
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test.each([
+ ['Chrome', 'Windows'],
+ ['Firefox', 'macOS'],
+ ['Safari', 'macOS'],
+])('Test on %s - %s', (browser, os) => {
+ qase.title('Cross-platform test');
+ qase.parameters({
+ Browser: browser,
+ OS: os,
+ });
+
+ expect(true).toBe(true);
+});
+```
+
+---
+
+## Multi-Project Support
+
+Send test results to multiple Qase projects simultaneously with different test case IDs for each project.
+
+For detailed configuration, examples, and troubleshooting, see the [Multi-Project Support Guide](MULTI_PROJECT.md).
+
+---
+
+## Running Tests
+
+### Basic Execution
+
+```bash
+# Run all tests
+npx jest
+
+# Run with Qase reporting enabled
+QASE_MODE=testops npx jest
+```
+
+### With Environment Variables
+
+```bash
+# Set project and token via environment
+QASE_MODE=testops \
+QASE_TESTOPS_PROJECT=DEMO \
+QASE_TESTOPS_API_TOKEN=your_token \
+npx jest
+```
+
+### With Test Plan
+
+```bash
+# Execute tests from a specific test plan
+QASE_MODE=testops \
+QASE_TESTOPS_PLAN_ID=123 \
+npx jest
+```
+
+### With Existing Test Run
+
+```bash
+# Report results to an existing test run
+QASE_MODE=testops \
+QASE_TESTOPS_RUN_ID=456 \
+npx jest
+```
+
+### Run Specific Tests
+
+```bash
+# Run specific test file
+npx jest path/to/test.spec.js
+
+# Run tests matching pattern
+npx jest --testPathPattern="auth"
+
+# Run tests in specific describe block
+npx jest --testNamePattern="Login"
```
-
-### Comment
---
-In addition to `test.step()`, this method can be used to provide any additional context to your test, it helps maintiain the code by clarifying the expected result of the test.
-```js
-test("A test case with qase.comment()", () => {
- /*
- * Please note, this comment is added to a Result, not to the Test case.
- */
- qase.comment("This comment is added to the result");
- // test logic here
+## Integration Patterns
+
+### Jest Lifecycle Hooks
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+describe('Test Suite', () => {
+ beforeAll(async () => {
+ await qase.step('Suite setup', async () => {
+ // Setup code
+ });
+ });
+
+ afterAll(async () => {
+ await qase.step('Suite teardown', async () => {
+ // Cleanup code
+ });
+ });
+
+ beforeEach(async () => {
+ await qase.step('Test setup', async () => {
+ // Before each test
+ });
+ });
+
+ afterEach(async () => {
+ await qase.step('Test cleanup', async () => {
+ // After each test
+ });
+ });
+
+ test(qase(1, 'Test case'), async () => {
+ await qase.step('Test execution', async () => {
+ expect(true).toBe(true);
+ });
+ });
+});
+```
+
+### Jest Config Reporter Options
+
+```javascript
+// jest.config.js
+module.exports = {
+ reporters: [
+ 'default',
+ [
+ 'jest-qase-reporter',
+ {
+ mode: 'testops',
+ debug: false,
+ testops: {
+ api: {
+ token: process.env.QASE_API_TOKEN,
+ },
+ project: 'DEMO',
+ run: {
+ title: 'Automated Jest Run',
+ description: 'CI/CD execution',
+ complete: true,
+ },
+ batch: {
+ size: 100,
+ },
+ },
+ },
+ ],
+ ],
+};
+```
+
+### Puppeteer Integration
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+const puppeteer = require('puppeteer');
+
+describe('Puppeteer Tests', () => {
+ let browser;
+ let page;
+
+ beforeAll(async () => {
+ browser = await puppeteer.launch();
+ page = await browser.newPage();
+ });
+
+ afterAll(async () => {
+ await browser.close();
+ });
+
+ test(qase(1, 'Navigate and screenshot'), async () => {
+ await qase.step('Navigate to page', async () => {
+ await page.goto('https://example.com');
+ });
+
+ await qase.step('Take screenshot', async () => {
+ const screenshot = await page.screenshot();
+ qase.attach({
+ name: 'page-screenshot.png',
+ content: screenshot,
+ contentType: 'image/png',
+ });
+ });
+
+ expect(await page.title()).toBe('Example Domain');
+ });
});
```
-
-### Attach
---
-This method can help attach one, or more files to the test's result. You can also add the file's contents directly from code. For example:
-```js
-test('Test result with attachment', async () => {
+## Common Use Cases
+
+### Report a Smoke Test Suite
- test("Test result with attachment", async () => {
-
- // To attach a single file
- await qase.attach({
- paths: "./attachments/test-file.txt",
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+describe('Smoke Tests', () => {
+ test(qase(101, 'Application loads'), () => {
+ qase.suite('Smoke Tests');
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'e2e',
});
- // Add multiple attachments.
- await qase.attach({ paths: ['/path/to/file', '/path/to/another/file'] });
+ expect(true).toBe(true);
+ });
+
+ test(qase(102, 'User can access homepage'), () => {
+ qase.suite('Smoke Tests');
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'e2e',
+ });
+
+ expect(true).toBe(true);
+ });
+});
+```
+
+### Attach Screenshot on Failure
- // Upload file's contents directly from code.
- await qase.attach({
- name: "attachment.txt",
- content: "Hello, world!",
- contentType: "text/plain",
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test(qase(1, 'Test with failure screenshot'), async () => {
+ try {
+ // Test logic that might fail
+ expect(false).toBe(true);
+ } catch (error) {
+ // Capture and attach screenshot on failure
+ const screenshot = Buffer.from('...', 'base64');
+ qase.attach({
+ name: 'failure-screenshot.png',
+ content: screenshot,
+ contentType: 'image/png',
});
- // test logic here
+ throw error;
+ }
+});
+```
+
+### Run Subset with Qase Test Plan
+
+```javascript
+// Use qasectl to filter tests by test plan
+// See: https://github.com/qase-tms/qasectl
+
+// Run tests:
+// npx jest --testNamePattern="$(cat qase.env | grep QASE_FILTERED_RESULTS | cut -d'=' -f2)"
+```
+
+### Report API Test Results
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+const axios = require('axios');
+
+describe('API Tests', () => {
+ test(qase(201, 'GET /users returns 200'), async () => {
+ qase.suite('API Tests');
+ qase.fields({ layer: 'api' });
+
+ await qase.step('Send GET request', async () => {
+ const response = await axios.get('https://api.example.com/users');
+
+ qase.attach({
+ name: 'response.json',
+ content: JSON.stringify(response.data, null, 2),
+ contentType: 'application/json',
+ });
+
+ expect(response.status).toBe(200);
+ });
+ });
+});
+```
+
+### Organize Tests by Feature
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+describe('Authentication Feature', () => {
+ test(qase(1, 'User registration'), () => {
+ qase.suite('Authentication\tRegistration');
+ qase.fields({ priority: 'high' });
+ expect(true).toBe(true);
+ });
+
+ test(qase(2, 'User login'), () => {
+ qase.suite('Authentication\tLogin');
+ qase.fields({ priority: 'high' });
+ expect(true).toBe(true);
+ });
+
+ test(qase(3, 'Password reset'), () => {
+ qase.suite('Authentication\tPassword Reset');
+ qase.fields({ priority: 'medium' });
+ expect(true).toBe(true);
+ });
});
```
-
-### Ignore
---
-If this method is added, the reporter will exclude the test’s result from the report sent to Qase. While the test will still executed by jest, its result will not be considered by the reporter.
-```js
-test("This test is executed by jest; however, it is NOT reported to Qase", () => {
- qase.ignore();
-// test logic here
+## Troubleshooting
+
+### Tests Not Appearing in Qase
+
+**Problem:** Tests run successfully but results are not visible in Qase.
+
+**Solutions:**
+
+1. Verify `mode` is set to `TestOps`:
+ ```bash
+ QASE_MODE=testops npx jest
+ ```
+
+2. Check API token has write permissions:
+ - Go to https://app.qase.io/apps
+ - Regenerate token if needed
+
+3. Verify project code is correct:
+ ```javascript
+ // Should match your project code in Qase
+ TestOps: {
+ project: 'DEMO', // Check this matches your project
+ }
+ ```
+
+4. Enable debug logging:
+ ```javascript
+ {
+ debug: true,
+ TestOps: { ... }
+ }
+ ```
+
+### Reporter Not Found Error
+
+**Problem:** `Cannot find module 'jest-qase-reporter'`
+
+**Solutions:**
+
+1. Install the package:
+ ```bash
+ npm install --save-dev jest-qase-reporter
+ ```
+
+2. Verify jest.config.js reporter configuration:
+ ```javascript
+ reporters: [
+ 'default',
+ ['jest-qase-reporter', { /* config */ }],
+ ]
+ ```
+
+### Qase Object Not Available in Tests
+
+**Problem:** `Qase is not defined` or `Cannot read property 'title' of undefined`
+
+**Solutions:**
+
+1. Import Qase at the top of your test file:
+ ```javascript
+ const { qase } = require('jest-qase-reporter/jest');
+ ```
+
+2. Ensure correct import path (note `/jest` suffix):
+ ```javascript
+ // Correct
+ require('jest-qase-reporter/jest');
+
+ // Incorrect
+ require('jest-qase-reporter');
+ ```
+
+### Results Not Grouped Correctly
+
+**Problem:** Test results appear in separate test runs instead of grouping together.
+
+**Solutions:**
+
+1. Use `--runInBand` to run tests serially:
+ ```bash
+ npx jest --runInBand
+ ```
+
+2. Or configure jest for serial execution:
+ ```javascript
+ // jest.config.js
+ module.exports = {
+ maxWorkers: 1,
+ };
+ ```
+
+### Attachments Not Uploading
+
+**Problem:** Files or content attachments are not visible in Qase.
+
+**Solutions:**
+
+1. Verify file path exists and is readable:
+ ```javascript
+ const fs = require('fs');
+ if (fs.existsSync('/path/to/file')) {
+ qase.attach({ paths: '/path/to/file' });
+ }
+ ```
+
+2. Check file size (large files may take time to upload)
+
+3. Enable debug logging to see upload status:
+ ```javascript
+ { debug: true }
+ ```
+
+4. Verify content type is specified:
+ ```javascript
+ qase.attach({
+ name: 'file.txt',
+ content: 'content',
+ contentType: 'text/plain', // Always specify
+ });
+ ```
+
+### Steps Not Appearing
+
+**Problem:** Test steps are not visible in Qase test results.
+
+**Solutions:**
+
+1. Ensure you're using async/await with steps:
+ ```javascript
+ // Correct
+ await qase.step('Step name', async () => {});
+
+ // Incorrect
+ qase.step('Step name', () => {});
+ ```
+
+2. Verify test function is async:
+ ```javascript
+ // Correct
+ test('test', async () => {
+ await qase.step('step', async () => {});
+ });
+
+ // Incorrect
+ test('test', () => {
+ qase.step('step', async () => {});
+ });
+ ```
+
+---
+
+## Complete Examples
+
+### Full Test Example
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+describe('Complete Example', () => {
+ test(qase([1, 2], 'Comprehensive test with all features'), async () => {
+ // Set metadata
+ qase.title('User can complete full registration flow');
+ qase.suite('Registration\tEnd-to-End');
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'e2e',
+ description: 'Tests complete user registration flow from start to finish',
+ preconditions: 'Application is running and database is accessible',
+ });
+ qase.parameters({
+ Browser: 'Chrome',
+ Environment: 'staging',
+ });
+
+ // Execute test with steps
+ await qase.step('Navigate to registration page', async () => {
+ // Navigation logic
+ qase.attach({
+ name: 'page-load.txt',
+ content: 'Page loaded successfully',
+ contentType: 'text/plain',
+ });
+ });
+
+ await qase.step('Fill registration form', async () => {
+ // Form filling logic
+ });
+
+ await qase.step('Submit form', async () => {
+ // Submit logic
+ });
+
+ await qase.step('Verify registration success', async () => {
+ // Verification logic
+ expect(true).toBe(true);
+ });
+
+ // Add final comment
+ qase.comment('Test completed successfully with all validations passing');
+ });
+});
```
+
+### Example Project Structure
+
+```
+my-project/
+├── qase.config.json
+├── jest.config.js
+├── tests/
+│ ├── auth.test.js
+│ ├── api.test.js
+│ └── integration.test.js
+└── package.json
+```
+
+---
+
+## See Also
+
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Attachments Guide](ATTACHMENTS.md)
+- [Steps Guide](STEPS.md)
+- [Multi-Project Support](MULTI_PROJECT.md)
+- [Upgrade Guide](UPGRADE.md)
diff --git a/qase-mocha/README.md b/qase-mocha/README.md
index 40149d29..998f6fc0 100644
--- a/qase-mocha/README.md
+++ b/qase-mocha/README.md
@@ -1,167 +1,284 @@
-# Qase TMS Mocha reporter
+# [Qase TestOps](https://qase.io) Mocha Reporter
-Publish results simple and easy.
+[](https://www.apache.org/licenses/LICENSE-2.0)
+[](https://www.npmjs.com/package/mocha-qase-reporter)
-To install the latest version, run:
+Qase Mocha Reporter enables seamless integration between your Mocha tests and [Qase TestOps](https://qase.io), providing automatic test result reporting, test case management, and comprehensive test analytics.
+
+## Features
+
+- Link automated tests to Qase test cases by ID
+- Auto-create test cases from your test code
+- Report test results with rich metadata (fields, attachments, steps)
+- Support for parameterized tests
+- Multi-project reporting support
+- Flexible configuration (file, environment variables, Mocha config)
+- Parallel execution support
+- Extra reporters support (spec, json, etc.)
+
+## Installation
```sh
-npm install -D mocha-qase-reporter
+npm install --save-dev mocha-qase-reporter
```
-## Getting started
-
-The Mocha reporter can auto-generate test cases
-and suites from your test data.
-Test results of subsequent test runs will match the same test cases
-as long as their names and file paths don't change.
+## Quick Start
-You can also annotate the tests with the IDs of existing test cases
-from Qase.io before executing tests. It's a more reliable way to bind
-autotests to test cases, that persists when you rename, move, or
-parameterize your tests.
+**1. Create `qase.config.json` in your project root:**
-For more information, see the [Usage Guide](docs/usage.md).
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "project": "YOUR_PROJECT_CODE",
+ "api": {
+ "token": "YOUR_API_TOKEN"
+ }
+ }
+}
+```
-For example:
+**2. Configure Mocha to use the reporter in `.mocharc.js`:**
-```typescript
-import { qase } from 'mocha-qase-reporter/mocha';
+```javascript
+module.exports = {
+ reporter: 'mocha-qase-reporter',
+ // ... other mocha options
+};
+```
-describe('My First Test', () => {
- it(qase(1,'Several ids'), () => {;
- expect(true).to.equal(true);
- });
+**3. Add Qase ID to your test:**
- // a test can check multiple test cases
- it(qase([2,3],'Correct test'), () => {
- expect(true).to.equal(true);
- });
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
- it.skip('Skipped test', () => {
- expect(true).to.equal(true);
+describe('Authentication', function() {
+ it(qase(1, 'User can login with valid credentials'), function() {
+ expect(login('user@example.com', 'password123')).to.equal(true);
});
});
```
-To execute Mocha tests and report them to Qase.io, run the command:
+**4. Run your tests:**
-```bash
-QASE_MODE=testops mocha
+```sh
+QASE_MODE=testops npx mocha
```
-or
+## Configuration
-```bash
-npm test
+The reporter is configured via (in order of priority):
+
+1. **Environment variables** (`QASE_*`, highest priority)
+2. **Config file** (`qase.config.json`)
+
+### Minimal Configuration
+
+| Option | Environment Variable | Description |
+|--------|---------------------|-------------|
+| `mode` | `QASE_MODE` | Set to `testops` to enable reporting |
+| `testops.project` | `QASE_TESTOPS_PROJECT` | Your Qase project code |
+| `testops.api.token` | `QASE_TESTOPS_API_TOKEN` | Your Qase API token |
+
+### Example `.mocharc.js`
+
+```javascript
+module.exports = {
+ reporter: 'mocha-qase-reporter',
+ require: ['@babel/register'],
+ spec: 'tests/**/*.spec.js',
+ timeout: 5000,
+};
+```
+
+### Example `qase.config.json`
+
+```json
+{
+ "mode": "testops",
+ "fallback": "report",
+ "debug": false,
+ "testops": {
+ "project": "YOUR_PROJECT_CODE",
+ "api": {
+ "token": "YOUR_API_TOKEN"
+ },
+ "run": {
+ "title": "Mocha Automated Run",
+ "complete": true
+ },
+ "batch": {
+ "size": 100
+ }
+ },
+ "report": {
+ "driver": "local",
+ "connection": {
+ "local": {
+ "path": "./build/qase-report",
+ "format": "json"
+ }
+ }
+ }
+}
```
-You can try it with the example project at [`examples/mocha`](../examples/mocha/).
+> **Full configuration reference:** See [qase-javascript-commons](../qase-javascript-commons/README.md) for all available options including logging, status mapping, execution plans, and more.
+
+## Usage
+
+### Link Tests with Test Cases
-
-
-
+Associate your tests with Qase test cases using test case IDs:
-A test run will be performed and available at:
+**Single ID:**
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+
+describe('User Management', function() {
+ it(qase(1, 'Create new user'), function() {
+ const user = createUser('john@example.com');
+ expect(user.email).to.equal('john@example.com');
+ });
+});
```
-https://app.qase.io/run/QASE_PROJECT_CODE
+
+**Multiple IDs:**
+
+```javascript
+describe('Login Tests', function() {
+ it(qase([1, 2, 3], 'Login works across different browsers'), function() {
+ const result = login('user@example.com', 'password');
+ expect(result.success).to.be.true;
+ });
+});
```
-### Multi-Project Support
+### Add Metadata
-Qase Mocha Reporter supports sending test results to multiple Qase projects simultaneously. You can specify different test case IDs for each project using `qase.projects(mapping, name)`.
+Enhance your tests with additional information:
-For detailed information, configuration, and examples, see the [Multi-Project Support Guide](docs/MULTI_PROJECT.md).
+```javascript
+it('User registration', function() {
+ qase.title('User can register with valid email and password');
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'api',
+ description: 'Tests user registration flow with validation',
+ });
+ qase.suite('Authentication / Registration');
-### Parallel execution
+ const user = register('newuser@example.com', 'SecurePass123');
+ expect(user.id).to.exist;
+});
+```
-The reporter supports parallel execution of tests.
+### Ignore Tests
-First, you need to create a new run in Qase.io. You can use
-the [Qase CLI](https://github.com/qase-tms/qasectl):
+Exclude specific tests from Qase reporting (test still runs, but results are not sent):
-```bash
-# Create a new test run
-qasectl testops run create --project DEMO --token token --title 'Mocha test run'
+```javascript
+it('Test under development', function() {
+ qase.ignore();
-# Save the run ID to the environment variable
-export QASE_TESTOPS_RUN_ID=$(< qase.env grep QASE_TESTOPS_RUN_ID | cut -d'=' -f2)
+ expect(true).to.be.true;
+});
```
-Then, you can run tests in parallel:
+### Test Result Statuses
+
+| Mocha Result | Qase Status |
+|--------------|-------------|
+| Passed | Passed |
+| Failed | Failed |
+| Pending | Skipped |
+| Skipped | Skipped |
+
+> For more usage examples, see the [Usage Guide](docs/usage.md).
+
+## Running Tests
+
+Run Mocha tests with Qase reporting:
```bash
-QASE_MODE=testops mocha --parallel
+# Run all tests
+QASE_MODE=testops npx mocha
+
+# Run with specific spec pattern
+QASE_MODE=testops npx mocha "tests/**/*.spec.js"
+
+# Run with grep filter
+QASE_MODE=testops npx mocha --grep "authentication"
+
+# Run with .mocharc.js configuration
+QASE_MODE=testops npx mocha
+
+# Run in parallel
+QASE_MODE=testops npx mocha --parallel
+
+# Run with extra reporters
+QASE_MODE=testops npx mocha --reporter mocha-qase-reporter --reporter-options extraReporters=spec
```
-After the tests are finished, you can complete the run:
+## Parallel Execution
+
+The reporter supports parallel execution of tests. First, create a new run in Qase.io using the [Qase CLI](https://github.com/qase-tms/qasectl):
```bash
+# Create a new test run
+qasectl testops run create --project DEMO --token token --title 'Mocha test run'
+
+# Save the run ID to the environment variable
+export QASE_TESTOPS_RUN_ID=$(< qase.env grep QASE_TESTOPS_RUN_ID | cut -d'=' -f2)
+
+# Run tests in parallel
+QASE_MODE=testops npx mocha --parallel
+
+# Complete the run after tests finish
qasectl testops run complete --project DEMO --token token --id $(echo $QASE_TESTOPS_RUN_ID)
```
-### Extra Reporters
+## Extra Reporters
The reporter supports additional reporters alongside the main Qase reporter. This allows you to use multiple output formats (e.g., console output and JSON reports) without the hanging issues that can occur with `mocha-multi-reporters` in parallel mode.
```bash
# Single extra reporter
-QASE_MODE=testops mocha --reporter mocha-qase-reporter --reporter-options extraReporters=spec
+QASE_MODE=testops npx mocha --reporter mocha-qase-reporter --reporter-options extraReporters=spec
# Multiple extra reporters
-QASE_MODE=testops mocha --reporter mocha-qase-reporter --reporter-options extraReporters=spec,json
+QASE_MODE=testops npx mocha --reporter mocha-qase-reporter --reporter-options extraReporters=spec,json
# With parallel execution
-QASE_MODE=testops mocha --reporter mocha-qase-reporter --reporter-options extraReporters=spec --parallel
+QASE_MODE=testops npx mocha --reporter mocha-qase-reporter --reporter-options extraReporters=spec --parallel
```
For detailed configuration options and examples, see the [Extra Reporters section](docs/usage.md#using-extra-reporters) in the usage guide.
-## Configuration
-
-Qase Mocha reporter can be configured in multiple ways:
-
-- using a separate config file `qase.config.json`,
-- using environment variables (they override the values from the configuration files).
-
-For a full list of configuration options, see
-the [Configuration reference](../qase-javascript-commons/README.md#configuration).
-
-Example `qase.config.json` config:
-
-```json
-{
- "mode": "testops",
- "debug": true,
- "testops": {
- "api": {
- "token": "api_key"
- },
- "project": "project_code",
- "run": {
- "complete": true
- }
- }
-}
-```
+## Requirements
-Also, you need to configure the reporter using the `.mocharc.js` file:
+- Node.js >= 14
+- Mocha >= 8.0.0
-```js
-// .mocharc.js
+## Documentation
-module.exports = {
- reporter: "mocha-qase-reporter",
- // ... other mocha options
-}
-```
+| Guide | Description |
+|-------|-------------|
+| [Usage Guide](docs/usage.md) | Complete usage reference with all methods and options |
+| [Attachments](docs/ATTACHMENTS.md) | Adding screenshots, logs, and files to test results |
+| [Steps](docs/STEPS.md) | Defining test steps for detailed reporting |
+| [Multi-Project Support](docs/MULTI_PROJECT.md) | Reporting to multiple Qase projects |
+| [Upgrade Guide](docs/UPGRADE.md) | Migration guide for breaking changes |
-## Requirements
+## Examples
-We maintain the reporter on [LTS versions of Node](https://nodejs.org/en/about/releases/).
+See the [examples directory](../examples/) for complete working examples:
-`mocha >= 10.2.0`
+- [Single project example](../examples/single/mocha/)
+- [Multi-project example](../examples/multiProject/mocha/)
-
+## License
-[auth]: https://developers.qase.io/#authentication
+Apache License 2.0. See [LICENSE](LICENSE) for details.
diff --git a/qase-mocha/changelog.md b/qase-mocha/changelog.md
index 819192ef..67cfbb50 100644
--- a/qase-mocha/changelog.md
+++ b/qase-mocha/changelog.md
@@ -59,7 +59,7 @@ Major release of the Mocha reporter package
## What's new
-- Added a `qase` function to allow specifying QaseID for tests.
+- Added a `Qase` function to allow specifying QaseID for tests.
- Marked the old syntax for QaseID as deprecated.
- Implemented functionality to capture console logs and include them as attachments in tests.
diff --git a/qase-mocha/docs/ATTACHMENTS.md b/qase-mocha/docs/ATTACHMENTS.md
new file mode 100644
index 00000000..959fa349
--- /dev/null
+++ b/qase-mocha/docs/ATTACHMENTS.md
@@ -0,0 +1,329 @@
+# Attachments in Mocha
+
+This guide covers how to attach files, screenshots, logs, and other content to your Qase test results.
+
+---
+
+## Overview
+
+Qase Mocha Reporter supports attaching various types of content to test results:
+
+- **Files** — Attach files from the filesystem
+- **Screenshots** — Attach images captured during test execution
+- **Logs** — Attach text logs or console output
+- **Binary data** — Attach any binary content from memory
+
+Attachments can be added to:
+- **Test cases** — Visible in the overall test result
+- **Test steps** — Visible in specific step results
+
+---
+
+## Attaching Files
+
+### From File Path
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+const assert = require('assert');
+
+describe('Attachment tests', function() {
+ it('Test with file attachment', function() {
+ qase.attach({ paths: './test/fixtures/test-file.txt' });
+
+ assert.strictEqual(1, 1);
+ });
+});
+```
+
+### Multiple Files
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+const assert = require('assert');
+
+describe('Attachment tests', function() {
+ it('Test with multiple attachments', function() {
+ qase.attach({
+ paths: [
+ './test/fixtures/file1.txt',
+ './test/fixtures/file2.log',
+ './test/fixtures/screenshot.png'
+ ]
+ });
+
+ assert.strictEqual(1, 1);
+ });
+});
+```
+
+---
+
+## Attaching Content from Memory
+
+### Text Content
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+const assert = require('assert');
+
+describe('Attachment tests', function() {
+ it('Test with text attachment', function() {
+ qase.attach({
+ name: 'log.txt',
+ content: 'Test execution log content',
+ contentType: 'text/plain',
+ });
+
+ assert.strictEqual(1, 1);
+ });
+});
+```
+
+### Binary Content (Screenshots)
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+const fs = require('fs');
+const assert = require('assert');
+
+describe('Attachment tests', function() {
+ it('Test with binary attachment', function() {
+ const screenshot = fs.readFileSync('./test/fixtures/screenshot.png');
+
+ qase.attach({
+ name: 'screenshot.png',
+ content: screenshot,
+ contentType: 'image/png',
+ });
+
+ assert.strictEqual(1, 1);
+ });
+});
+```
+
+### JSON Data
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+const assert = require('assert');
+
+describe('Attachment tests', function() {
+ it('Test with JSON attachment', function() {
+ const data = {
+ userId: 123,
+ status: 'active',
+ timestamp: new Date().toISOString(),
+ };
+
+ qase.attach({
+ name: 'test-data.json',
+ content: JSON.stringify(data, null, 2),
+ contentType: 'application/json',
+ });
+
+ assert.strictEqual(1, 1);
+ });
+});
+```
+
+---
+
+## Attaching to Steps
+
+Attach content to a specific test step:
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+const assert = require('assert');
+
+describe('Step attachment tests', function() {
+ it('Test with step attachments', function() {
+ qase.step('Initialize test data', () => {
+ const testData = { user: 'testuser', role: 'admin' };
+
+ qase.attach({
+ name: 'init-data.json',
+ content: JSON.stringify(testData, null, 2),
+ contentType: 'application/json',
+ });
+ });
+
+ qase.step('Execute test', () => {
+ qase.attach({
+ name: 'execution-log.txt',
+ content: 'Test execution completed successfully',
+ contentType: 'text/plain',
+ });
+
+ assert.strictEqual(1, 1);
+ });
+ });
+});
+```
+
+---
+
+## Method Reference
+
+### `qase.attach()`
+
+Attach content to the test case.
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `paths` | `string` or `string[]` | No* | Path(s) to file(s) to attach |
+| `content` | `string` or `Buffer` | No* | Content to attach |
+| `name` | `string` | No | Custom filename (auto-detected from path) |
+| `contentType` | `string` | No | MIME type (auto-detected from extension) |
+
+\* Either `paths` or `content` must be provided, but not both.
+
+**CommonJS:**
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+
+qase.attach({ paths: './path/to/file.txt' });
+```
+
+**ES Modules:**
+```javascript
+import { qase } from 'mocha-qase-reporter/mocha';
+
+qase.attach({ paths: './path/to/file.txt' });
+```
+
+---
+
+## MIME Types
+
+Common MIME types are auto-detected based on file extension:
+
+| Extension | MIME Type |
+|-----------|-----------|
+| `.png` | `image/png` |
+| `.jpg`, `.jpeg` | `image/jpeg` |
+| `.gif` | `image/gif` |
+| `.svg` | `image/svg+xml` |
+| `.txt` | `text/plain` |
+| `.log` | `text/plain` |
+| `.json` | `application/json` |
+| `.xml` | `application/xml` |
+| `.html` | `text/html` |
+| `.csv` | `text/csv` |
+| `.pdf` | `application/pdf` |
+| `.zip` | `application/zip` |
+
+For other file types, specify `contentType` explicitly.
+
+---
+
+## Common Use Cases
+
+### Debug Logs
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+const assert = require('assert');
+
+describe('Debug logging', function() {
+ it('Test with debug logs', function() {
+ const debugLogs = [
+ 'Test started at ' + new Date().toISOString(),
+ 'Initializing test environment',
+ 'Running test logic',
+ 'Test completed successfully'
+ ];
+
+ qase.attach({
+ name: 'debug.log',
+ content: debugLogs.join('\n'),
+ contentType: 'text/plain',
+ });
+
+ assert.strictEqual(1, 1);
+ });
+});
+```
+
+### File Attachments
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+const fs = require('fs');
+const assert = require('assert');
+
+describe('File attachment tests', function() {
+ it('Test with generated file attachment', function() {
+ const reportData = 'Test Report\n============\nStatus: Passed\n';
+ const filePath = './test/output/report.txt';
+
+ fs.writeFileSync(filePath, reportData);
+
+ qase.attach({ paths: filePath });
+
+ assert.strictEqual(1, 1);
+
+ // Cleanup
+ fs.unlinkSync(filePath);
+ });
+});
+```
+
+### API Testing Logs
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+const assert = require('assert');
+
+describe('API tests', function() {
+ it('API request with response logging', async function() {
+ const response = await fetch('https://api.example.com/users');
+ const data = await response.json();
+
+ qase.attach({
+ name: 'api-response.json',
+ content: JSON.stringify(data, null, 2),
+ contentType: 'application/json',
+ });
+
+ assert.strictEqual(response.status, 200);
+ });
+});
+```
+
+---
+
+## Troubleshooting
+
+### Attachments Not Appearing
+
+1. Verify the file path exists and is readable
+2. Check file permissions
+3. Enable debug logging to see upload status:
+ ```json
+ {
+ "debug": true
+ }
+ ```
+
+### Large Files
+
+Large attachments may slow down test execution. Consider:
+- Compressing logs before attaching
+- Using selective logging (e.g., only on failure)
+- Setting reasonable size limits
+
+### Binary Data Issues
+
+When attaching binary data, always specify:
+- `name` with appropriate extension
+- `contentType` if extension doesn't match content type
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Steps Guide](STEPS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-mocha/docs/MULTI_PROJECT.md b/qase-mocha/docs/MULTI_PROJECT.md
index 0dd08e3c..646b55f2 100644
--- a/qase-mocha/docs/MULTI_PROJECT.md
+++ b/qase-mocha/docs/MULTI_PROJECT.md
@@ -6,6 +6,8 @@ Qase Mocha Reporter supports sending test results to multiple Qase projects simu
* Different projects track the same functionality with different test case IDs
* You want to maintain separate test runs for different environments or teams
+---
+
## Configuration
For detailed configuration options, refer to the [qase-javascript-commons README](../../qase-javascript-commons/README.md#multi-project-support).
@@ -14,40 +16,158 @@ For detailed configuration options, refer to the [qase-javascript-commons README
Set `mode` to `testops_multi` in your Mocha reporter options (e.g. in `.mocharc.js` or `qase.config.json`) and add the `testops_multi` section with `default_project` and `projects`.
+**Example configuration:**
+
+```json
+{
+ "mode": "testops_multi",
+ "testops_multi": {
+ "default_project": "PROJ1",
+ "projects": [
+ {
+ "code": "PROJ1",
+ "api": {
+ "token": "your_api_token_for_proj1"
+ }
+ },
+ {
+ "code": "PROJ2",
+ "api": {
+ "token": "your_api_token_for_proj2"
+ }
+ }
+ ]
+ }
+}
+```
+
+---
+
## Using `qase.projects(mapping, name)`
Use `qase.projects(mapping, name)` to set the test title with multi-project markers. Use the returned string as the first argument to `it()`:
```javascript
-const { qase } = require('mocha-qase-reporter');
+const { qase } = require('mocha-qase-reporter/mocha');
// Single project
-it(qase(100, 'login flow'), function () { ... });
+it(qase(100, 'login flow'), function () {
+ expect(true).to.equal(true);
+});
// Multi-project
-it(qase.projects({ PROJ1: [100], PROJ2: [200] }, 'login flow'), function () { ... });
+it(qase.projects({ PROJ1: [100], PROJ2: [200] }, 'login flow'), function () {
+ expect(true).to.equal(true);
+});
// Multiple IDs per project
-it(qase.projects({ PROJ1: [10, 11], PROJ2: [20] }, 'checkout'), function () { ... });
+it(qase.projects({ PROJ1: [10, 11], PROJ2: [20] }, 'checkout'), function () {
+ expect(true).to.equal(true);
+});
```
+**Key points:**
+
+- Single project with single ID: `it(qase(100, 'test name'), function () { ... })`
+- Multi-project: `it(qase.projects({ PROJ1: [100], PROJ2: [200] }, 'test name'), function () { ... })`
+- Multiple IDs per project: `it(qase.projects({ PROJ1: [10, 11], PROJ2: [20] }, 'test name'), function () { ... })`
+
Project codes (e.g. `PROJ1`, `PROJ2`) must match `testops_multi.projects[].code` in your config.
+---
+
## Tests Without Project Mapping
Tests that do not use `qase.projects()` and have no `(Qase PROJ: ids)` in the title are sent to the `default_project`. If they use `qase(id, name)` (single-project), that ID is used for the default project.
+---
+
## Important Notes
-1. **Project codes must match**: Codes in `qase.projects({ PROJ1: [1], ... })` must match `testops_multi.projects[].code`.
+1. **Project codes must match**: Codes in `qase.projects({ PROJ1: [1], ... })` must match `testops_multi.projects[].code` in config.
2. **Mode**: Set `mode` to `testops_multi` in reporter config.
3. **Title format**: The helper produces a title like `Name (Qase PROJ1: 1,2) (Qase PROJ2: 3)` so the reporter can parse the mapping.
+4. **API tokens**: Each project in `testops_multi.projects[]` can have its own API token for separate authentication.
+
+---
## Examples
See the [multi-project Mocha example](../../examples/multiProject/mocha/) for a complete runnable setup.
+### Complete Example
+
+Here's a complete Mocha test file showing multi-project usage:
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+const { expect } = require('chai');
+
+describe('Multi-project test suite', function () {
+ // Test reported to two projects
+ it(qase.projects({ PROJ1: [1], PROJ2: [2] }, 'User can login successfully'), function () {
+ const username = 'testuser';
+ const password = 'password123';
+
+ expect(username).to.be.a('string');
+ expect(password).to.be.a('string');
+ });
+
+ // Test with multiple case IDs per project
+ it(qase.projects({ PROJ1: [10, 11], PROJ2: [20] }, 'Checkout process works'), function () {
+ const cart = { items: 2, total: 99.99 };
+
+ expect(cart.items).to.be.greaterThan(0);
+ expect(cart.total).to.be.greaterThan(0);
+ });
+
+ // Combining multi-project with other Qase methods
+ it(qase.projects({ PROJ1: [100], PROJ2: [200] }, 'User registration'), function () {
+ qase.title('Complete user registration flow');
+ qase.fields({ severity: 'critical', priority: 'high' });
+
+ expect(true).to.equal(true);
+ });
+
+ // Single-project test (uses default_project)
+ it(qase(50, 'Test reported to default project'), function () {
+ expect(1 + 1).to.equal(2);
+ });
+
+ // Test without Qase metadata (goes to default_project without case ID)
+ it('Regular test without Qase tracking', function () {
+ expect(true).to.equal(true);
+ });
+});
+```
+
+---
+
## Troubleshooting
-* Verify `mode` is `testops_multi` and project codes in `qase.projects()` match the config.
-* Ensure the first argument to `it()` is the string returned by `qase.projects(mapping, name)` (or an equivalent title with markers).
+### Results Not Appearing in All Projects
+
+* Verify `mode` is `testops_multi` (not `TestOps`) in reporter config
+* Check that project codes in `qase.projects()` match config codes exactly (case-sensitive)
+* Ensure each project has a valid API token with write permissions
+* Ensure the first argument to `it()` is the string returned by `qase.projects(mapping, name)`
+
+### Wrong Test Cases Linked
+
+* Verify the mapping object has correct project codes as keys
+* Check that test case IDs exist in the respective projects
+* Enable debug logging to see how the reporter parses multi-project markers
+
+### Default Project Not Working
+
+* Ensure `default_project` is set in `testops_multi` config
+* Verify the default project code matches one of the projects in the `projects` array
+* Tests without `qase.projects()` will only report to the default project
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Examples](../../examples/multiProject/mocha/)
diff --git a/qase-mocha/docs/STEPS.md b/qase-mocha/docs/STEPS.md
new file mode 100644
index 00000000..e57adcb4
--- /dev/null
+++ b/qase-mocha/docs/STEPS.md
@@ -0,0 +1,402 @@
+# Test Steps in Mocha
+
+This guide covers how to define and report test steps for detailed execution tracking in Qase.
+
+---
+
+## Overview
+
+Test steps provide granular visibility into test execution. Each step is reported separately, showing:
+
+- Step name and description
+- Step status (passed/failed)
+- Step duration
+- Attachments (if any)
+- Error details (on failure)
+
+---
+
+## Defining Steps
+
+### Using Step Callbacks
+
+Define steps using synchronous callbacks with the test context:
+
+```javascript
+const assert = require('assert');
+
+describe('Test suite', function() {
+ it('Test with multiple steps', function() {
+ this.step('Initialize the environment', function() {
+ // Set up test environment
+ });
+
+ this.step('Test Core Functionality', function() {
+ // Exercise core functionality
+ });
+
+ this.step('Verify Expected Behavior', function() {
+ // Assert expected behavior
+ assert.strictEqual(1, 1);
+ });
+ });
+});
+```
+
+**Important:** Mocha steps use `function()` syntax (not arrow functions) to access the test context via `this`. Steps are synchronous.
+
+### Step Parameters
+
+Steps can include parameters for dynamic naming:
+
+```javascript
+const assert = require('assert');
+
+describe('Test suite', function() {
+ it('Test with dynamic step names', function() {
+ const username = 'john@example.com';
+
+ this.step(`Login as user ${username}`, function() {
+ // Login logic
+ });
+
+ this.step(`Verify ${username} profile loaded`, function() {
+ // Verification logic
+ assert.strictEqual(true, true);
+ });
+ });
+});
+```
+
+---
+
+## Nested Steps
+
+Create hierarchical step structures:
+
+```javascript
+const assert = require('assert');
+
+describe('Test suite', function() {
+ it('Test with nested steps', function() {
+ this.step('Complete user registration', function() {
+ this.step('Fill registration form', function() {
+ // Fill form fields
+ });
+
+ this.step('Submit registration', function() {
+ // Click submit button
+ });
+ });
+
+ this.step('Verify registration success', function() {
+ assert.strictEqual(true, true);
+ });
+ });
+});
+```
+
+---
+
+## Steps with Expected Result and Data
+
+Define expected results and data for steps:
+
+```javascript
+const assert = require('assert');
+
+describe('Test suite', function() {
+ it('Test with expected results', function() {
+ this.step(
+ 'Click button',
+ function() {
+ // Click action
+ },
+ 'Button should be clicked',
+ 'Button data'
+ );
+
+ this.step(
+ 'Fill form',
+ function() {
+ // Form filling action
+ },
+ 'Form should be filled',
+ 'Form input data'
+ );
+
+ this.step(
+ 'Submit form',
+ function() {
+ // Submit action
+ },
+ 'Form should be submitted',
+ 'Form submission data'
+ );
+
+ assert.strictEqual(1, 1);
+ });
+});
+```
+
+**Signature:**
+```typescript
+this.step(
+ name: string,
+ callback: () => void,
+ expectedResult?: string,
+ data?: string
+): void
+```
+
+---
+
+## Steps with Attachments
+
+Attach content to a specific step:
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+const assert = require('assert');
+
+describe('Test suite', function() {
+ it('Test with step attachments', function() {
+ this.step('Capture application state', function() {
+ const state = JSON.stringify({ user: 'john', status: 'active' });
+
+ qase.attach({
+ name: 'app-state.json',
+ content: state,
+ contentType: 'application/json',
+ });
+ });
+
+ this.step('Verify state', function() {
+ // Assertions
+ assert.strictEqual(true, true);
+ });
+ });
+});
+```
+
+---
+
+## Step Status
+
+Steps automatically inherit status from execution:
+
+| Execution | Step Status |
+|-----------|-------------|
+| Completes normally | Passed |
+| Throws Error/AssertionError | Failed |
+| Other exception | Invalid |
+
+---
+
+## Best Practices
+
+### Keep Steps Atomic
+
+Each step should represent a single action:
+
+```javascript
+// Good: One action per step
+this.step('Navigate to login page', function() {
+ // Navigation logic
+});
+
+this.step('Enter username', function() {
+ // Fill username
+});
+
+// Avoid: Multiple actions in one step
+this.step('Fill form and submit', function() { // Too broad
+ // Multiple actions
+});
+```
+
+### Use Descriptive Names
+
+```javascript
+// Good: Clear action description
+this.step('Verify user is redirected to dashboard', function() {
+ assert.strictEqual(window.location.pathname, '/dashboard');
+});
+
+// Avoid: Vague names
+this.step('Check page', function() {
+ assert.strictEqual(window.location.pathname, '/dashboard');
+});
+```
+
+### Include Context in Step Names
+
+```javascript
+// Good: Include relevant context
+this.step(`Add product '${productName}' to cart`, function() {
+ // Add to cart logic
+});
+
+// Better than generic:
+this.step('Add product', function() {
+ // Add to cart logic
+});
+```
+
+---
+
+## Common Patterns
+
+### Page Object Steps
+
+```javascript
+const assert = require('assert');
+
+class LoginPage {
+ constructor(testContext) {
+ this.ctx = testContext;
+ }
+
+ login(username, password) {
+ this.ctx.step(`Enter username: ${username}`, function() {
+ // Fill username field
+ });
+
+ this.ctx.step('Enter password', function() {
+ // Fill password field (don't log password)
+ });
+
+ this.ctx.step('Click login button', function() {
+ // Click submit
+ });
+ }
+}
+
+describe('Authentication', function() {
+ it('User can login', function() {
+ const loginPage = new LoginPage(this);
+ loginPage.login('user@example.com', 'password');
+ });
+});
+```
+
+### API Testing Steps
+
+```javascript
+const assert = require('assert');
+const fetch = require('node-fetch');
+
+describe('API tests', function() {
+ it('API returns correct user data', function() {
+ let response;
+
+ this.step('Send GET request to /api/users/1', function() {
+ response = fetch('https://api.example.com/users/1');
+ });
+
+ this.step('Verify response status is 200', function() {
+ response.then(res => {
+ assert.strictEqual(res.status, 200);
+ });
+ });
+
+ this.step('Verify response contains user data', function() {
+ response.then(res => res.json()).then(data => {
+ assert.strictEqual(data.id, 1);
+ assert.ok(data.name);
+ });
+ });
+ });
+});
+```
+
+### Setup/Teardown Steps
+
+```javascript
+const assert = require('assert');
+
+describe('User tests', function() {
+ beforeEach(function() {
+ this.step('Setup: Create test user', function() {
+ // Create user in database
+ });
+
+ this.step('Setup: Initialize session', function() {
+ // Set up user session
+ });
+ });
+
+ afterEach(function() {
+ this.step('Cleanup: Delete test user', function() {
+ // Remove user from database
+ });
+ });
+
+ it('Test user operations', function() {
+ this.step('Perform user action', function() {
+ // Test logic
+ assert.strictEqual(true, true);
+ });
+ });
+});
+```
+
+---
+
+## Troubleshooting
+
+### Steps Not Appearing
+
+1. Verify you're using `function()` syntax (not arrow functions) to access `this.step`
+2. Check that steps are executed within a test context
+3. Enable debug logging to trace step recording
+4. Ensure the reporter is properly configured in `.mocharc.js`
+
+```javascript
+// Incorrect: Arrow function loses 'this' context
+it('Test', () => {
+ this.step('Step name', () => { // 'this' is undefined
+ // Logic
+ });
+});
+
+// Correct: Using function() syntax
+it('Test', function() {
+ this.step('Step name', function() {
+ // Logic
+ });
+});
+```
+
+### Nested Steps Flattened
+
+Ensure you're using the synchronous callbacks correctly for nesting:
+
+```javascript
+// Correct: Nested callbacks
+this.step('Parent step', function() {
+ this.step('Child step', function() {
+ // Child step logic
+ });
+});
+
+// Incorrect: Sequential, not nested
+this.step('Step 1', function() {
+ // Step 1 logic
+});
+this.step('Step 2', function() { // Not nested under Step 1
+ // Step 2 logic
+});
+```
+
+### Step Duration Shows 0
+
+Steps need measurable execution time. Very fast steps may show 0ms duration.
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Attachments Guide](ATTACHMENTS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-mocha/docs/UPGRADE.md b/qase-mocha/docs/UPGRADE.md
new file mode 100644
index 00000000..aa9e0868
--- /dev/null
+++ b/qase-mocha/docs/UPGRADE.md
@@ -0,0 +1,327 @@
+# Upgrade Guide: Mocha Reporter
+
+This guide covers migration steps between major versions of the Qase Mocha Reporter.
+
+---
+
+## Version History
+
+| Version | Release Date | Node.js Support | Key Changes |
+|---------|--------------|-----------------|-------------|
+| 1.2.0 | January 2026 | >= 14 | Current stable release with enhanced metadata support |
+| 1.1.0 | December 2025 | >= 14 | Multi-project support and improved step handling |
+| 1.0.0 | August 2025 | >= 14 | Initial release with unified qase-javascript-commons |
+
+---
+
+## Upgrading to 1.x
+
+### Breaking Changes
+
+The mocha-qase-reporter is currently in its first major version series (1.x). No migration from a previous major version is required.
+
+### Current Version Features
+
+Version 1.2.0 includes:
+
+- Full support for Qase TestOps API with batch result upload
+- Test case linking via wrapper function pattern: `it(qase(id, 'name'), callback)`
+- Rich metadata support: titles, fields, suites, parameters, comments
+- Synchronous test steps with `qase.step()` (Mocha-style)
+- File and content-based attachments with `qase.attach()`
+- Multi-project support for reporting to multiple Qase projects
+- Extra reporters support for combining with other Mocha reporters
+- Flexible configuration via `qase.config.json` or `.mocharc.js`
+
+---
+
+## Configuration
+
+### Current Format (v1.x)
+
+Configuration uses the modern qase-javascript-commons format:
+
+```javascript
+// .mocharc.js
+module.exports = {
+ reporters: [
+ 'spec', // Keep default Mocha spec reporter
+ [
+ 'mocha-qase-reporter',
+ {
+ mode: 'testops',
+ debug: false,
+ testops: {
+ api: {
+ token: process.env.QASE_API_TOKEN,
+ },
+ project: 'DEMO',
+ run: {
+ title: 'Mocha Automated Run',
+ description: 'Test run from CI/CD pipeline',
+ complete: true,
+ },
+ batch: {
+ size: 100,
+ },
+ },
+ },
+ ],
+ ],
+};
+```
+
+**Alternative: qase.config.json**
+
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "api": {
+ "token": "api_token_here"
+ },
+ "project": "DEMO",
+ "run": {
+ "complete": true
+ }
+ }
+}
+```
+
+**Command Line:**
+
+```bash
+QASE_MODE=testops npx mocha
+```
+
+---
+
+## Import Pattern
+
+### Current Import (v1.x)
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+```
+
+**ES Modules:**
+
+```javascript
+import { qase } from 'mocha-qase-reporter/mocha';
+```
+
+**Note:** The `/mocha` subpath is required to access the `Qase` helper function.
+
+---
+
+## API Reference
+
+### Test Case ID Linking
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+
+describe('User Authentication', () => {
+ // Single test case ID
+ it(qase(1, 'User can login'), () => {
+ expect(true).to.equal(true);
+ });
+
+ // Multiple test case IDs
+ it(qase([1, 2], 'Multiple IDs'), () => {
+ expect(true).to.equal(true);
+ });
+});
+```
+
+### Metadata Methods
+
+```javascript
+it('Test with metadata', () => {
+ qase.title('Custom test title');
+ qase.fields({ severity: 'critical', priority: 'high' });
+ qase.suite('Authentication / Login');
+ qase.comment('This test covers the main login flow');
+ qase.parameters({ environment: 'staging', browser: 'Chrome' });
+
+ expect(true).to.equal(true);
+});
+```
+
+### Steps (Synchronous)
+
+**Important:** Mocha uses synchronous callbacks for steps (no async/await):
+
+```javascript
+it('Test with steps', function () {
+ qase.step('Initialize environment', () => {
+ // Setup code
+ });
+
+ qase.step('Perform action', () => {
+ // Test logic
+ });
+
+ qase.step('Verify result', () => {
+ // Assertions
+ expect(result).to.equal(expected);
+ });
+});
+```
+
+**Note:** Use `function()` syntax (not arrow functions) when you need access to Mocha's `this` context:
+
+```javascript
+it('Test with context', function () {
+ this.timeout(5000); // Access Mocha's this context
+
+ qase.step('Step 1', () => {
+ // Step code
+ });
+});
+```
+
+### Attachments
+
+```javascript
+// Path-based attachment
+qase.attach({ paths: '/path/to/log.txt' });
+
+// Multiple files
+qase.attach({ paths: ['/path/to/file1.txt', '/path/to/file2.log'] });
+
+// Content-based attachment
+qase.attach({
+ name: 'test-log.txt',
+ content: 'Test execution log content',
+ contentType: 'text/plain',
+});
+```
+
+---
+
+## Extra Reporters
+
+Mocha-qase-reporter supports combining with other Mocha reporters:
+
+```javascript
+// .mocharc.js
+module.exports = {
+ reporters: [
+ 'spec', // Default console output
+ 'json', // JSON output
+ ['mocha-qase-reporter', { /* Qase options */ }],
+ ],
+};
+```
+
+This allows you to maintain local test reports while also sending results to Qase.
+
+---
+
+## Compatibility Notes
+
+### Node.js Version Support
+
+- **Current (1.2.0):** Node.js >= 14
+
+### Mocha Version Support
+
+- **Current (1.2.0):** Mocha >= 9.0.0
+- Tested with Mocha 10.x and 11.x
+
+### Framework Compatibility
+
+- CommonJS and ES Modules supported
+- TypeScript support with full type definitions
+- Works with all Mocha interfaces (BDD, TDD, QUnit, Exports)
+- Compatible with other Mocha reporters (extra reporters)
+
+---
+
+## Troubleshooting
+
+### Common Issues
+
+#### Issue: Module not found after installation
+
+**Solution:** Ensure you're importing from the correct subpath:
+
+```javascript
+// Correct
+const { qase } = require('mocha-qase-reporter/mocha');
+
+// Incorrect - missing /mocha subpath
+const { qase } = require('mocha-qase-reporter');
+```
+
+#### Issue: Reporter not running
+
+**Solution:** Verify that `QASE_MODE=testops` is set when running tests:
+
+```bash
+QASE_MODE=testops npx mocha
+```
+
+Or configure `mode: 'testops'` in your `.mocharc.js` or `qase.config.json`.
+
+#### Issue: Steps not working in async tests
+
+**Solution:** Mocha steps use synchronous callbacks, not async/await:
+
+```javascript
+// Correct - synchronous callbacks
+it('Test', () => {
+ qase.step('Step 1', () => {
+ // Code
+ });
+});
+
+// Incorrect - async/await not supported for steps in Mocha
+it('Test', async () => {
+ await qase.step('Step 1', async () => {
+ // This won't work correctly
+ });
+});
+```
+
+#### Issue: Cannot access Mocha's this context
+
+**Solution:** Use `function()` syntax instead of arrow functions:
+
+```javascript
+// Correct - function syntax preserves this context
+it('Test', function () {
+ this.timeout(5000);
+ qase.step('Step', () => { /* ... */ });
+});
+
+// Incorrect - arrow function loses this context
+it('Test', () => {
+ this.timeout(5000); // Error: cannot read property of undefined
+});
+```
+
+---
+
+## Getting Help
+
+If you encounter issues:
+
+1. Check the [GitHub Issues](https://github.com/qase-tms/qase-javascript/issues)
+2. Review the CHANGELOG
+3. Open a new issue with:
+ - Current version (1.2.0)
+ - Mocha version
+ - Error messages
+ - Configuration file (without sensitive data)
+ - Steps to reproduce
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Multi-Project Support](MULTI_PROJECT.md)
+- CHANGELOG
diff --git a/qase-mocha/docs/usage.md b/qase-mocha/docs/usage.md
index 289f3a90..5b80c5e5 100644
--- a/qase-mocha/docs/usage.md
+++ b/qase-mocha/docs/usage.md
@@ -1,290 +1,821 @@
# Qase Integration in Mocha
-This guide demonstrates how to integrate Qase with Mocha, providing instructions on how to add Qase IDs, titles,
-fields, suites, comments, and file attachments to your test cases.
+This guide provides comprehensive instructions for integrating Qase with Mocha.
+
+> **Configuration:** For complete configuration reference including all available options, environment variables, and examples, see the [qase-javascript-commons README](../../qase-javascript-commons/README.md#configuration).
---
-## Adding QaseID to a Test
+## Table of Contents
+
+- [Adding QaseID](#adding-qaseid)
+- [Adding Title](#adding-title)
+- [Adding Fields](#adding-fields)
+- [Adding Suite](#adding-suite)
+- [Ignoring Tests](#ignoring-tests)
+- [Muting Tests](#muting-tests)
+- [Working with Attachments](#working-with-attachments)
+- [Working with Steps](#working-with-steps)
+- [Working with Parameters](#working-with-parameters)
+- [Multi-Project Support](#multi-project-support)
+- [Using Extra Reporters](#using-extra-reporters)
+- [Running Tests](#running-tests)
+- [Integration Patterns](#integration-patterns)
+- [Common Use Cases](#common-use-cases)
+- [Troubleshooting](#troubleshooting)
+- [Complete Examples](#complete-examples)
+
+- [See Also](#see-also)
+---
-To associate a QaseID with a test in Mocha, use the `qase()` function. This function accepts a single integer or
-an array of integers representing the test's ID(s) in Qase, and optionally a custom test name.
+## Adding QaseID
-### Example
+Link your automated tests to existing test cases in Qase by specifying the test case ID.
+
+### Single ID
```javascript
const { qase } = require('mocha-qase-reporter/mocha');
-describe('User Authentication', function() {
- it(qase(1, 'Successful login'), function() {
- expect(true).to.equal(true);
+describe('Authentication', function() {
+ it(qase(1, 'User can login with valid credentials'), function() {
+ const result = login('user@example.com', 'password123');
+ expect(result.success).to.equal(true);
});
+});
+```
+
+### Multiple IDs
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
- it(qase([2, 3, 4], 'Multiple test cases'), function() {
- expect(true).to.equal(true);
+describe('Cross-Browser Testing', function() {
+ it(qase([1, 2, 3], 'Login works across Chrome, Firefox, and Edge'), function() {
+ const result = login('user@example.com', 'password');
+ expect(result.success).to.be.true;
});
});
```
----
+### Multi-Project Support
-## Adding a Title to a Test
+To send test results to multiple Qase projects simultaneously, see the [Multi-Project Support Guide](MULTI_PROJECT.md).
-You can provide a custom title for your test using the `this.title()` method within the test function.
-If no title is provided, the test's default name will be used.
+---
-### Example
+## Adding Title
+
+Set a custom title for the test case (overrides auto-generated title):
```javascript
const { qase } = require('mocha-qase-reporter/mocha');
-describe('User Authentication', function() {
- it('test with custom title', function() {
- this.title('Custom Test Title');
- expect(true).to.equal(true);
- });
+describe('Login Tests', function() {
+ it('login test', function() {
+ qase.title('User successfully logs in with valid credentials');
- it('test with default title', function() {
- expect(true).to.equal(true);
+ const user = login('user@example.com', 'password123');
+ expect(user).to.exist;
});
});
```
---
-## Adding Fields to a Test
+## Adding Fields
-You can add additional metadata to a test case using the `this.fields()` method. This allows you
-to specify multiple fields to enhance test case information in Qase.
+Add metadata to your test cases using fields. Both system and custom fields are supported.
### System Fields
-- `description` — Description of the test case.
-- `preconditions` — Preconditions for the test case.
-- `postconditions` — Postconditions for the test case.
-- `severity` — Severity of the test case (e.g., `critical`, `major`).
-- `priority` — Priority of the test case (e.g., `high`, `low`).
-- `layer` — Test layer (e.g., `UI`, `API`).
+| Field | Description | Example Values |
+|-------|-------------|----------------|
+| `description` | Test case description | Any text |
+| `preconditions` | Test preconditions | Any text (supports Markdown) |
+| `postconditions` | Test postconditions | Any text |
+| `severity` | Test severity | `blocker`, `critical`, `major`, `normal`, `minor`, `trivial` |
+| `priority` | Test priority | `high`, `medium`, `low` |
+| `layer` | Test layer | `e2e`, `api`, `unit` |
### Example
```javascript
const { qase } = require('mocha-qase-reporter/mocha');
-describe('User Authentication', function() {
- it('test with fields', function() {
- this.fields({
- severity: 'high',
- priority: 'medium',
- description: 'Login functionality test',
- custom_field: 'value'
+describe('User Management', function() {
+ it('create new user', function() {
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'api',
+ description: 'Verifies that admin can create a new user account via API',
+ preconditions: 'Admin user is authenticated with valid token',
+ postconditions: 'New user record exists in database',
});
- expect(true).to.equal(true);
+
+ const user = createUser({ email: 'newuser@example.com', role: 'user' });
+ expect(user.id).to.exist;
+ expect(user.email).to.equal('newuser@example.com');
});
});
```
---
-## Adding a Suite to a Test
+## Adding Suite
-To assign a suite or sub-suite to a test, use the `this.suite()` method. It can receive a suite
-name, and optionally a sub-suite, both as strings.
+Organize tests into suites and sub-suites:
-### Example
+### Simple Suite
```javascript
const { qase } = require('mocha-qase-reporter/mocha');
-describe('User Authentication', function() {
- it('test with suite', function() {
- this.suite('Authentication');
- expect(true).to.equal(true);
+describe('Payment Tests', function() {
+ it('process payment', function() {
+ qase.suite('API Tests / Payment');
+
+ const payment = processPayment({ amount: 100, currency: 'USD' });
+ expect(payment.status).to.equal('success');
});
+});
+```
- it('test with nested suite', function() {
- this.suite('Authentication\\Login\\Edge Cases');
- expect(true).to.equal(true);
+### Nested Suites
+
+```javascript
+describe('User Tests', function() {
+ it('user registration', function() {
+ qase.suite('API Tests\\User Management\\Registration');
+
+ const user = registerUser('newuser@example.com', 'SecurePass123');
+ expect(user.verified).to.be.false;
});
});
```
---
-## Ignoring a Test in Qase
+## Ignoring Tests
-To exclude a test from being reported to Qase (while still executing the test in Mocha), use the `this.ignore()`
-method. The test will run, but its result will not be sent to Qase.
+Exclude a test from Qase reporting. The test still executes, but results are not sent to Qase:
-### Example
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+
+describe('Feature Tests', function() {
+ it('test under development', function() {
+ qase.ignore();
+
+ const result = testNewFeature();
+ expect(result).to.be.true;
+ });
+});
+```
+
+---
+
+## Muting Tests
+
+Mark a test as muted. Muted tests are reported but do not affect the test run status:
```javascript
const { qase } = require('mocha-qase-reporter/mocha');
-describe('User Authentication', function() {
- it('ignored test', function() {
- this.ignore();
- expect(true).to.equal(true);
+describe('Known Issues', function() {
+ it('flaky test with known issue', function() {
+ qase.mute();
+
+ const result = performFlakyOperation();
+ expect(result).to.equal('expected');
});
});
```
---
-## Adding Parameters to a Test
+## Working with Attachments
-You can add parameters to a test case using the `this.parameters()` method. This method accepts
-an object with parameter names and values.
+Attach files, screenshots, logs, and other content to your test results.
-### Example
+### Attach File from Path
```javascript
const { qase } = require('mocha-qase-reporter/mocha');
-describe('User Authentication', function() {
- it('test with parameters', function() {
- this.parameters({
- browser: 'chrome',
- environment: 'staging'
+describe('File Processing Tests', function() {
+ it('process document', function() {
+ qase.attach({ paths: './fixtures/document.pdf' });
+
+ const result = processDocument('./fixtures/document.pdf');
+ expect(result.success).to.be.true;
+ });
+});
+```
+
+### Attach Multiple Files
+
+```javascript
+it('test with multiple attachments', function() {
+ qase.attach({
+ paths: [
+ './fixtures/config.json',
+ './fixtures/data.csv',
+ './logs/execution.log',
+ ],
+ });
+
+ const result = runTest();
+ expect(result).to.be.true;
+});
+```
+
+### Attach Content from Code
+
+```javascript
+it('test with log attachment', function() {
+ const testLog = `
+ Test execution log
+ Step 1: Initialize test environment
+ Step 2: Execute test scenario
+ Step 3: Verify results
+ Step 4: Clean up
+ `;
+
+ qase.attach({
+ name: 'execution.log',
+ content: testLog,
+ contentType: 'text/plain',
+ });
+
+ expect(true).to.be.true;
+});
+```
+
+### Attach JSON Data
+
+```javascript
+it('API test with response data', function() {
+ const response = {
+ statusCode: 200,
+ body: {
+ id: 1,
+ name: 'John Doe',
+ email: 'john@example.com',
+ },
+ };
+
+ qase.attach({
+ name: 'api-response.json',
+ content: JSON.stringify(response, null, 2),
+ contentType: 'application/json',
+ });
+
+ expect(response.statusCode).to.equal(200);
+});
+```
+
+### Supported MIME Types
+
+Common MIME types are auto-detected. You can also specify explicitly:
+
+| Extension | MIME Type |
+|-----------|-----------|
+| `.png` | `image/png` |
+| `.jpg`, `.jpeg` | `image/jpeg` |
+| `.gif` | `image/gif` |
+| `.txt` | `text/plain` |
+| `.json` | `application/json` |
+| `.xml` | `application/xml` |
+| `.html` | `text/html` |
+| `.pdf` | `application/pdf` |
+
+> For more details, see [Attachments Guide](ATTACHMENTS.md).
+
+---
+
+## Working with Steps
+
+Define test steps for detailed reporting in Qase.
+
+### Using Synchronous Function
+
+Mocha steps use synchronous callbacks:
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+
+describe('Shopping Cart', function() {
+ it('add item to cart', function() {
+ qase.step('Navigate to product page', function() {
+ const page = navigateTo('/products/laptop');
+ expect(page.loaded).to.be.true;
+ });
+
+ qase.step('Add product to cart', function() {
+ const result = addToCart('laptop-123');
+ expect(result.success).to.be.true;
+ });
+
+ qase.step('Verify cart count', function() {
+ const cartCount = getCartItemCount();
+ expect(cartCount).to.equal(1);
+ });
+ });
+});
+```
+
+### Nested Steps
+
+```javascript
+it('complete checkout process', function() {
+ qase.step('Add items to cart', function() {
+ qase.step('Add first item', function() {
+ addToCart('laptop-123');
+ });
+
+ qase.step('Add second item', function() {
+ addToCart('mouse-456');
+ });
+ });
+
+ qase.step('Complete checkout', function() {
+ qase.step('Enter shipping info', function() {
+ setShippingAddress('123 Main St');
+ });
+
+ qase.step('Submit payment', function() {
+ const result = submitPayment({ cardNumber: '4242424242424242' });
+ expect(result.success).to.be.true;
});
- expect(true).to.equal(true);
});
});
```
+### Steps with Expected Result
+
+```javascript
+it('form validation test', function() {
+ qase.step(
+ 'Enter invalid email',
+ function() {
+ const result = validateEmail('invalid-email');
+ expect(result.valid).to.be.false;
+ },
+ 'Validation should fail for invalid email format',
+ );
+
+ qase.step(
+ 'Verify error message',
+ function() {
+ const error = getValidationError();
+ expect(error).to.equal('Please enter a valid email');
+ },
+ 'Error message displays correctly',
+ );
+});
+```
+
+> For more details, see [Steps Guide](STEPS.md).
+
---
-## Adding Group Parameters to a Test
+## Working with Parameters
-To add group parameters to a test case, use the `this.groupParameters()` method. This method
-accepts an object with group parameter names and values.
+Report parameterized test data to Qase.
-### Example
+### Basic Parameterized Test
```javascript
const { qase } = require('mocha-qase-reporter/mocha');
-describe('User Authentication', function() {
- it('test with group parameters', function() {
- this.parameters({
- browser: 'chrome',
- environment: 'staging'
+describe('Login with different users', function() {
+ const users = [
+ { username: 'admin', password: 'admin123', role: 'admin' },
+ { username: 'user', password: 'user123', role: 'user' },
+ { username: 'guest', password: 'guest123', role: 'guest' },
+ ];
+
+ users.forEach(function(user) {
+ it(`login as ${user.role}`, function() {
+ qase.parameters({
+ username: user.username,
+ role: user.role,
+ });
+
+ const result = login(user.username, user.password);
+ expect(result.success).to.be.true;
+ expect(result.role).to.equal(user.role);
});
- this.groupParameters({
- test_group: 'authentication',
- test_type: 'smoke'
+ });
+});
+```
+
+### Group Parameters
+
+```javascript
+describe('Cross-environment tests', function() {
+ const environments = ['development', 'staging', 'production'];
+
+ environments.forEach(function(env) {
+ it(`test in ${env}`, function() {
+ qase.parameters({
+ environment: env,
+ baseUrl: getBaseUrl(env),
+ });
+
+ qase.groupParameters({
+ 'Test Group': 'Environment Testing',
+ 'Priority': 'High',
+ });
+
+ const health = checkHealth(env);
+ expect(health.status).to.equal('ok');
});
- expect(true).to.equal(true);
});
});
```
---
-## Adding Steps to a Test
+## Multi-Project Support
-You can add custom steps to your test cases using the `this.step()` method. Each step should
-have a title and optionally a function. You can also provide an expected result and input data for each step, which will be displayed in Qase.
+Send test results to multiple Qase projects simultaneously with different test case IDs for each project.
-### Example
+For detailed configuration, examples, and troubleshooting, see the [Multi-Project Support Guide](MULTI_PROJECT.md).
+
+---
+
+## Integration Patterns
+
+### Mocha Hooks with Qase
+
+Integrate Qase reporting with Mocha's before/after hooks:
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+
+describe('Test Suite with Hooks', function() {
+ before(function() {
+ qase.comment('Suite-level setup: Database initialized with test data');
+ initializeDatabase();
+ });
+
+ after(function() {
+ qase.comment('Suite-level teardown: Test data cleaned up');
+ cleanupDatabase();
+ });
+
+ beforeEach(function() {
+ resetState();
+ });
+
+ afterEach(function() {
+ if (this.currentTest.state === 'failed') {
+ qase.attach({
+ name: 'failure-log.txt',
+ content: `Test failed: ${this.currentTest.title}`,
+ contentType: 'text/plain',
+ });
+ }
+ });
+
+ it('test with hooks', function() {
+ expect(getDatabaseState()).to.equal('ready');
+ });
+});
+```
+
+### BDD Interface Example
```javascript
const { qase } = require('mocha-qase-reporter/mocha');
describe('User Authentication', function() {
- it('test with steps', function() {
- this.step('Navigate to login page', function() {
- // Step implementation
- });
- this.step('Enter credentials', function() {
- // Step implementation
+ describe('Login', function() {
+ it(qase(1, 'should allow valid user to login'), function() {
+ qase.fields({
+ severity: 'critical',
+ layer: 'e2e',
+ });
+
+ const result = login('user@example.com', 'password123');
+ expect(result.success).to.be.true;
});
- this.step('Submit login form', function() {
- // Step implementation
+
+ it(qase(2, 'should reject invalid credentials'), function() {
+ qase.fields({
+ severity: 'critical',
+ layer: 'e2e',
+ });
+
+ const result = login('user@example.com', 'wrongpassword');
+ expect(result.success).to.be.false;
+ expect(result.error).to.equal('Invalid credentials');
});
- this.step('Verify successful login', function() {
- // Step implementation
+ });
+});
+```
+
+### TDD Interface Example
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+
+suite('User Management', function() {
+ test(qase(10, 'createUser returns new user object'), function() {
+ qase.fields({
+ layer: 'unit',
+ priority: 'high',
});
- expect(true).to.equal(true);
+
+ const user = createUser('test@example.com');
+ assert.ok(user);
+ assert.equal(user.email, 'test@example.com');
+ });
+
+ test(qase(11, 'deleteUser removes user from system'), function() {
+ const user = createUser('temp@example.com');
+ const result = deleteUser(user.id);
+ assert.equal(result, true);
});
});
```
-### Example with Expected Result and Data
+### Chai Assertion Patterns
```javascript
const { qase } = require('mocha-qase-reporter/mocha');
+const { expect } = require('chai');
-describe('User Authentication', function() {
- it('test with steps including expected results and data', function() {
- this.step('Click button', function() {
- // Click action
- }, 'Button should be clicked', 'Button data');
-
- this.step('Fill form', function() {
- // Form filling action
- }, 'Form should be filled', 'Form input data');
-
- expect(true).to.equal(true);
+describe('API Endpoint Tests', function() {
+ it(qase(20, 'GET /users returns user list'), function() {
+ const response = request('GET', '/users');
+
+ expect(response).to.have.status(200);
+ expect(response.body).to.be.an('array');
+ expect(response.body).to.have.lengthOf.at.least(1);
+ expect(response.body[0]).to.have.property('id');
+ expect(response.body[0]).to.have.property('email');
+ });
+});
+```
+
+### Root-Level Hooks
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+
+before(function() {
+ qase.comment('Global setup: Initialize test environment');
+ global.testEnv = initializeTestEnvironment();
+});
+
+after(function() {
+ qase.comment('Global teardown: Clean up test environment');
+ cleanupTestEnvironment(global.testEnv);
+});
+
+describe('Test Suite 1', function() {
+ it('test 1', function() {
+ expect(global.testEnv).to.exist;
+ });
+});
+
+describe('Test Suite 2', function() {
+ it('test 2', function() {
+ expect(global.testEnv).to.exist;
});
});
```
---
-## Attaching Files to a Test
+## Common Use Cases
-You can attach files to test results using the `this.attach()` method. This method supports attaching files
-with content, paths, or media types.
+### Report with Chai Assertions
-### Example
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+const { expect } = require('chai');
+
+describe('User Service', function() {
+ it(qase(30, 'creates user with valid data'), function() {
+ qase.fields({
+ layer: 'api',
+ severity: 'critical',
+ });
+
+ const userData = {
+ email: 'newuser@example.com',
+ name: 'New User',
+ role: 'user',
+ };
+
+ const user = createUser(userData);
+
+ expect(user).to.be.an('object');
+ expect(user).to.have.property('id');
+ expect(user.email).to.equal(userData.email);
+ expect(user.name).to.equal(userData.name);
+ expect(user.role).to.equal(userData.role);
+
+ qase.attach({
+ name: 'created-user.json',
+ content: JSON.stringify(user, null, 2),
+ contentType: 'application/json',
+ });
+ });
+});
+```
+
+### Use BDD Interface
```javascript
const { qase } = require('mocha-qase-reporter/mocha');
-describe('User Authentication', function() {
- it('test with attachments', function() {
- // Attach text content
- this.attach({
- name: 'attachment.log',
- content: 'Login successful',
- contentType: 'text/plain'
+describe('Feature: User Login', function() {
+ context('Given a registered user', function() {
+ context('When user enters valid credentials', function() {
+ it(qase(31, 'Then user should be logged in'), function() {
+ const result = login('user@example.com', 'password123');
+ expect(result.authenticated).to.be.true;
+ });
});
-
- // Attach JSON data
- const userData = { username: 'testuser', status: 'logged_in' };
- this.attach({
- name: 'user-data.json',
- content: JSON.stringify(userData, null, 2),
- contentType: 'application/json'
+
+ context('When user enters invalid credentials', function() {
+ it(qase(32, 'Then user should see error message'), function() {
+ const result = login('user@example.com', 'wrongpassword');
+ expect(result.authenticated).to.be.false;
+ expect(result.error).to.exist;
+ });
});
-
- expect(true).to.equal(true);
});
});
```
----
+### Timeout Handling with Qase
+
+```javascript
+describe('Long Running Operations', function() {
+ it(qase(33, 'processes large dataset'), function() {
+ this.timeout(10000); // Set timeout for specific test
-## Adding Comments to a Test
+ qase.parameters({
+ timeout: '10000ms',
+ datasetSize: 'large',
+ });
-You can add comments to test results using the `this.comment()` method. This is useful for providing additional
-context or explanations about test execution.
+ const startTime = Date.now();
+ const result = processLargeDataset();
+ const duration = Date.now() - startTime;
-### Example
+ qase.comment(`Processing completed in ${duration}ms`);
+
+ expect(result.processed).to.be.true;
+ expect(duration).to.be.below(10000);
+ });
+});
+```
+
+### Dynamic Test Generation with Reporting
```javascript
const { qase } = require('mocha-qase-reporter/mocha');
-describe('User Authentication', function() {
- it('test with comments', function() {
- // Add comment about the test execution
- this.comment('Login test completed successfully');
-
- // Add comment with additional context
- this.comment('User was redirected to dashboard as expected');
-
- expect(true).to.equal(true);
+describe('API Endpoint Tests', function() {
+ const endpoints = [
+ { path: '/users', method: 'GET', qaseId: 40 },
+ { path: '/posts', method: 'GET', qaseId: 41 },
+ { path: '/comments', method: 'GET', qaseId: 42 },
+ ];
+
+ endpoints.forEach(function(endpoint) {
+ it(qase(endpoint.qaseId, `${endpoint.method} ${endpoint.path} returns 200`), function() {
+ qase.parameters({
+ method: endpoint.method,
+ path: endpoint.path,
+ });
+
+ const response = request(endpoint.method, endpoint.path);
+ expect(response.statusCode).to.equal(200);
+
+ qase.attach({
+ name: `${endpoint.method}-${endpoint.path.replace('/', '')}.json`,
+ content: JSON.stringify(response.body, null, 2),
+ contentType: 'application/json',
+ });
+ });
+ });
+});
+```
+
+### Async/Await Pattern
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+
+describe('Async Operations', function() {
+ it(qase(50, 'fetches user data asynchronously'), async function() {
+ qase.step('Fetch user from API', function() {
+ // Step implementation
+ });
+
+ const user = await fetchUserAsync(1);
+
+ expect(user).to.exist;
+ expect(user.id).to.equal(1);
+ });
+});
+```
+
+### Error Handling and Reporting
+
+```javascript
+describe('Error Scenarios', function() {
+ it(qase(51, 'handles invalid input gracefully'), function() {
+ qase.fields({
+ severity: 'major',
+ layer: 'unit',
+ });
+
+ try {
+ const result = processInput(null);
+ expect.fail('Should have thrown an error');
+ } catch (error) {
+ qase.attach({
+ name: 'error-details.txt',
+ content: error.stack,
+ contentType: 'text/plain',
+ });
+
+ expect(error.message).to.equal('Invalid input');
+ }
+ });
+});
+```
+
+### Data-Driven Testing with CSV
+
+```javascript
+const { qase } = require('mocha-qase-reporter/mocha');
+const fs = require('fs');
+const path = require('path');
+
+describe('Email Validation', function() {
+ const csvData = fs.readFileSync(path.join(__dirname, 'test-data.csv'), 'utf-8');
+ const rows = csvData.split('\n').slice(1); // Skip header
+
+ rows.forEach(function(row, index) {
+ const [email, expected] = row.split(',');
+
+ it(`validates email: ${email}`, function() {
+ qase.parameters({
+ email: email,
+ expected: expected,
+ row: index + 2,
+ });
+
+ const result = validateEmail(email.trim());
+ expect(result.valid.toString()).to.equal(expected.trim());
+ });
+ });
+});
+```
+
+### Integration with Database
+
+```javascript
+describe('Database Operations', function() {
+ let db;
+
+ before(function() {
+ db = connectToDatabase();
+ });
+
+ after(function() {
+ db.close();
+ });
+
+ it(qase(60, 'inserts record into database'), function() {
+ qase.step('Insert user record', function() {
+ const user = { email: 'dbuser@example.com', name: 'DB User' };
+ const insertId = db.insert('users', user);
+
+ qase.parameters({
+ insertId: insertId,
+ table: 'users',
+ });
+
+ expect(insertId).to.be.a('number');
+ });
+
+ qase.step('Verify record exists', function() {
+ const user = db.findOne('users', { email: 'dbuser@example.com' });
+ expect(user).to.exist;
+ expect(user.name).to.equal('DB User');
+ });
});
});
```
@@ -303,13 +834,13 @@ You can configure extra reporters using the `extraReporters` option in the `qase
```bash
# Single extra reporter
-QASE_MODE=testops mocha --reporter mocha-qase-reporter --reporter-options extraReporters=spec
+QASE_MODE=testops npx mocha --reporter mocha-qase-reporter --reporter-options extraReporters=spec
# Multiple extra reporters
-QASE_MODE=testops mocha --reporter mocha-qase-reporter --reporter-options extraReporters=spec,json
+QASE_MODE=testops npx mocha --reporter mocha-qase-reporter --reporter-options extraReporters=spec,json
# With parallel execution
-QASE_MODE=testops mocha --reporter mocha-qase-reporter --reporter-options extraReporters=spec --parallel
+QASE_MODE=testops npx mocha --reporter mocha-qase-reporter --reporter-options extraReporters=spec --parallel
```
#### qase.config.json
@@ -355,31 +886,307 @@ npm run test:extra
npm run test:extra-parallel
# Multiple reporters
-QASE_MODE=testops mocha --reporter mocha-qase-reporter --reporter-options extraReporters=spec,json --parallel
+QASE_MODE=testops npx mocha --reporter mocha-qase-reporter --reporter-options extraReporters=spec,json --parallel
```
---
-### Parallel Execution
+## Running Tests
-The reporter supports parallel execution of tests. First, create a new run in Qase.io using the Qase CLI:
+### Basic Execution
```bash
-# Create a new test run
-qasectl testops run create --project DEMO --token token --title 'Mocha test run'
+# Run all tests
+QASE_MODE=testops npx mocha
-# Save the run ID to the environment variable
-export QASE_TESTOPS_RUN_ID=$(< qase.env grep QASE_TESTOPS_RUN_ID | cut -d'=' -f2)
+# Run with spec pattern
+QASE_MODE=testops npx mocha "tests/**/*.spec.js"
```
-Then run tests in parallel:
+### With Environment Variables
```bash
-QASE_MODE=testops mocha --parallel
+# Enable Qase reporting
+QASE_MODE=testops npx mocha
+
+# Specify project code
+QASE_MODE=testops QASE_TESTOPS_PROJECT=DEMO npx mocha
+
+# Use specific API token
+QASE_MODE=testops QASE_TESTOPS_API_TOKEN=your_token npx mocha
```
-After the tests are finished, complete the run:
+### With Test Plan
```bash
+# Run tests linked to specific test plan
+QASE_MODE=testops QASE_TESTOPS_PLAN_ID=123 npx mocha
+```
+
+### With Existing Test Run
+
+```bash
+# Report to existing test run
+QASE_MODE=testops QASE_TESTOPS_RUN_ID=456 npx mocha
+```
+
+### Run with Filters
+
+```bash
+# Run tests matching grep pattern
+QASE_MODE=testops npx mocha --grep "authentication"
+
+# Run tests not matching grep pattern
+QASE_MODE=testops npx mocha --grep "integration" --invert
+
+# Run tests in specific file
+QASE_MODE=testops npx mocha tests/auth.spec.js
+```
+
+### Parallel Execution
+
+```bash
+# Create test run
+qasectl testops run create --project DEMO --token token --title 'Mocha test run'
+
+# Save run ID
+export QASE_TESTOPS_RUN_ID=$(< qase.env grep QASE_TESTOPS_RUN_ID | cut -d'=' -f2)
+
+# Run tests in parallel
+QASE_MODE=testops npx mocha --parallel
+
+# Complete the run
qasectl testops run complete --project DEMO --token token --id $(echo $QASE_TESTOPS_RUN_ID)
```
+
+---
+
+## Troubleshooting
+
+### Tests Not Appearing in Qase
+
+**Issue:** Tests execute but results don't appear in Qase TestOps.
+
+**Solutions:**
+
+1. Verify `mode` is set to `TestOps` (not `off` or `report`)
+2. Check API token has write permissions
+3. Verify project code is correct
+4. Check for errors in console output
+5. Enable debug logging: set `debug: true` in configuration
+
+```json
+{
+ "mode": "testops",
+ "debug": true,
+ "testops": {
+ "project": "DEMO"
+ }
+}
+```
+
+### Reporter Not Configured
+
+**Issue:** Error: "No reporter configured"
+
+**Solution:** Add reporter configuration to `.mocharc.js`:
+
+```javascript
+module.exports = {
+ reporter: 'mocha-qase-reporter',
+ // ... other options
+};
+```
+
+Or use command line:
+
+```bash
+npx mocha --reporter mocha-qase-reporter
+```
+
+### Attachments Not Uploading
+
+**Issue:** Files not appearing in test results.
+
+**Solutions:**
+
+1. Verify file path exists and is readable
+2. Check file size is within limits
+3. Enable debug logging to see upload status
+
+### This Context Issues with Arrow Functions
+
+**Issue:** Cannot access `Qase` methods when using arrow functions.
+
+**Solution:** Use regular `function()` syntax (not arrow functions) for Mocha tests:
+
+```javascript
+// Correct
+it('test', function() {
+ qase.title('Title');
+ expect(true).to.be.true;
+});
+
+// Incorrect - arrow function loses this context
+it('test', () => {
+ qase.title('Title'); // May not work
+ expect(true).to.be.true;
+});
+```
+
+### Results Going to Wrong Test Cases
+
+**Issue:** Test results appear under incorrect test case IDs.
+
+**Solutions:**
+
+1. Verify QaseID matches the test case ID in Qase
+2. Check for duplicate IDs in your test suite
+3. Verify you're using the correct project code
+4. Ensure test names haven't changed significantly
+
+### Timeout Issues
+
+**Issue:** Long-running tests timing out.
+
+**Solutions:**
+
+1. Increase timeout for specific test:
+ ```javascript
+ it('long running test', function() {
+ this.timeout(10000);
+ // Test code
+ });
+ ```
+
+2. Increase timeout globally in `.mocharc.js`:
+ ```javascript
+ module.exports = {
+ timeout: 5000,
+ };
+ ```
+
+### BDD vs TDD Interface Confusion
+
+**Issue:** Tests not recognized with different interfaces.
+
+**Solution:** Ensure you're using correct interface methods:
+
+**BDD (default):**
+- `describe()`, `context()`, `it()`, `specify()`
+- `before()`, `after()`, `beforeEach()`, `afterEach()`
+
+**TDD:**
+- `suite()`, `test()`
+- `setup()`, `teardown()`, `suiteSetup()`, `suiteTeardown()`
+
+### Import Path Issues
+
+**Issue:** Error: "Cannot find module 'mocha-qase-reporter/mocha'"
+
+**Solution:** Ensure you're using the correct import path:
+
+```javascript
+// Correct
+const { qase } = require('mocha-qase-reporter/mocha');
+
+// Incorrect
+const { qase } = require('mocha-qase-reporter');
+```
+
+### Parallel Mode Hangs
+
+**Issue:** Tests hang when running in parallel mode.
+
+**Solutions:**
+
+1. Use extra reporters instead of mocha-multi-reporters
+2. Create test run before parallel execution
+3. Ensure all tests are independent (no shared state)
+
+### Steps Not Showing in Qase
+
+**Issue:** Step definitions not appearing in test results.
+
+**Solution:** Ensure you're using `qase.step()` with proper syntax:
+
+```javascript
+qase.step('Step name', function() {
+ // Step implementation
+});
+```
+
+---
+
+## Complete Examples
+
+### Full Test Example
+
+```javascript
+const { qase } = require('mocha-qase-reporter');
+
+describe('Complete Example', function() {
+ it(qase([1, 2], 'Comprehensive test with all features'), async function() {
+ // Set metadata
+ qase.title('User can complete full registration flow');
+ qase.suite('Registration\tEnd-to-End');
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'e2e',
+ description: 'Tests complete user registration flow from start to finish',
+ preconditions: 'Application is running and database is accessible',
+ });
+ qase.parameters({
+ Browser: 'Chrome',
+ Environment: 'staging',
+ });
+
+ // Execute test with steps
+ await qase.step('Navigate to registration page', async function() {
+ // Navigation logic
+ qase.attach({
+ name: 'page-load.txt',
+ content: 'Page loaded successfully',
+ contentType: 'text/plain',
+ });
+ });
+
+ await qase.step('Fill registration form', async function() {
+ // Form filling logic
+ });
+
+ await qase.step('Submit form', async function() {
+ // Submit logic
+ });
+
+ await qase.step('Verify email confirmation', async function() {
+ // Verification logic
+ });
+ });
+});
+```
+
+### Example Project Structure
+
+```
+my-project/
+├── qase.config.json
+├── test/
+│ ├── auth.test.js
+│ ├── checkout.test.js
+│ └── ...
+├── helpers/
+│ └── ...
+└── package.json
+```
+
+---
+
+## See Also
+
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Attachments Guide](ATTACHMENTS.md)
+- [Steps Guide](STEPS.md)
+- [Multi-Project Support](MULTI_PROJECT.md)
+- [Upgrade Guide](UPGRADE.md)
diff --git a/qase-newman/README.md b/qase-newman/README.md
index a7db2670..3607cc67 100644
--- a/qase-newman/README.md
+++ b/qase-newman/README.md
@@ -1,90 +1,274 @@
-# Qase TMS Newman reporter
+# [Qase TestOps](https://qase.io) Newman Reporter
-Publish results simple and easy.
+[](https://www.apache.org/licenses/LICENSE-2.0)
+[](https://www.npmjs.com/package/newman-qase-reporter)
-To install the latest version, run:
+Qase Newman Reporter enables seamless integration between your Newman/Postman collection tests and [Qase TestOps](https://qase.io), providing automatic test result reporting, test case management, and comprehensive test analytics.
-```bash
-npm install newman-reporter-qase
-```
-
-## Example of usage
+## Features
-### Define in tests
+- Link automated tests to Qase test cases by ID using special comments
+- Auto-create test cases from your Postman test scripts
+- Report test results with parameterized data
+- Multi-project reporting support
+- Flexible configuration (file, environment variables)
-The Newman reporter has the ability to auto-generate test cases
-and suites from your test data.
+> **Note:** Newman integration is unique - tests are defined in Postman collections, and Qase IDs are specified via special comments in test scripts, not via programmatic API imports.
-But if necessary, you can independently register the ID of already
-existing test cases from TMS before the executing tests.
-Example:
+## Installation
-```js
-//qase: 10
-// Qase: 1, 2, 3
-// qase: 4 5 6 14
-pm.test('expect response be 200', function() {
- pm.response.to.be.info
-})
+```bash
+npm install --save-dev newman-reporter-qase
```
-### Execute rom CLI:
+## Quick Start
-```
-QASE_MODE=testops newman run ./sample-collection.json -r qase
+**1. Create `qase.config.json` in your project root:**
+
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "project": "YOUR_PROJECT_CODE",
+ "api": {
+ "token": "YOUR_API_TOKEN"
+ }
+ }
+}
```
-
-
-
+**2. Add Qase ID to your Postman test using special comments:**
-A test run will be performed and available at:
+In your Postman collection test script:
-```
-https://app.qase.io/run/QASE_PROJECT_CODE
+```javascript
+// qase: 1
+pm.test('Response status is 200', function() {
+ pm.response.to.have.status(200);
+});
```
-
-
-
+**3. Run your Newman tests with Qase reporter:**
-You can find more information about using the reporter [here](./docs/usage.md).
+```bash
+QASE_MODE=testops newman run ./collection.json -r qase
+```
## Configuration
-Qase Newman reporter can be configured in multiple ways:
+The reporter is configured via (in order of priority):
+
+1. **Environment variables** (`QASE_*`)
+2. **Config file** (`qase.config.json`)
-- using a separate config file `qase.config.json`,
-- using environment variables (they override the values from the configuration files).
+### Minimal Configuration
-For a full list of configuration options, see
-the [Configuration reference](../qase-javascript-commons/README.md#configuration).
+| Option | Environment Variable | Description |
+|--------|---------------------|-------------|
+| `mode` | `QASE_MODE` | Set to `testops` to enable reporting |
+| `testops.project` | `QASE_TESTOPS_PROJECT` | Your Qase project code |
+| `testops.api.token` | `QASE_TESTOPS_API_TOKEN` | Your Qase API token |
-Example `qase.config.json` config:
+### Example `qase.config.json`
```json
{
"mode": "testops",
- "debug": true,
+ "fallback": "report",
"testops": {
+ "project": "YOUR_PROJECT_CODE",
"api": {
- "token": "api_key"
+ "token": "YOUR_API_TOKEN"
},
- "project": "project_code",
"run": {
- "complete": true
+ "title": "Newman Automated Run"
+ },
+ "batch": {
+ "size": 100
+ }
+ },
+ "report": {
+ "driver": "local",
+ "connection": {
+ "local": {
+ "path": "./build/qase-report",
+ "format": "json"
+ }
}
}
}
```
+> **Full configuration reference:** See [qase-javascript-commons](../qase-javascript-commons/README.md) for all available options including logging, status mapping, execution plans, and more.
+
+## Usage
+
+### Link Tests with Test Cases
+
+Associate your Postman tests with Qase test cases using special comments in your test scripts:
+
+**Single ID:**
+```javascript
+// qase: 10
+pm.test('Response is successful', function() {
+ pm.response.to.be.info;
+});
+```
+
+**Multiple IDs:**
+```javascript
+// Qase: 1, 2, 3
+pm.test('Verify user data', function() {
+ pm.expect(pm.response.json()).to.have.property('user');
+});
+```
+
+**Alternative formats:**
+```javascript
+// qase: 4 5 6 14
+pm.test('Check multiple conditions', function() {
+ pm.response.to.have.status(200);
+});
+```
+
+> **Important:** The comment must be on the line immediately before the `pm.test()` call.
+
+### Add Parameters
+
+Newman supports parameterized tests when using data files. Specify which parameters to report using special comments:
+
+```javascript
+// qase.parameters: userId, user.name
+pm.test('User ID is correct', function() {
+ var jsonData = pm.response.json();
+ pm.expect(jsonData.userId).to.eql(pm.iterationData.get('userid'));
+});
+```
+
+You can also specify parameters at the collection or folder level:
+
+```json
+{
+ "item": [{
+ "name": "Folder Name",
+ "event": [{
+ "listen": "test",
+ "script": {
+ "exec": [
+ "// qase.parameters: userId, user.name"
+ ]
+ }
+ }]
+ }]
+}
+```
+
+### Auto-collect All Parameters
+
+To automatically report all parameters from data files without specifying them:
+
+```json
+{
+ "framework": {
+ "newman": {
+ "autoCollectParams": true
+ }
+ }
+}
+```
+
+### Ignore Tests
+
+Newman does not support ignoring individual tests. All tests in the collection will be executed and reported (unless filtered by Newman's own mechanisms like `--folder` flag).
+
+### Test Result Statuses
+
+| Newman Result | Qase Status |
+|---------------|-------------|
+| Passed | passed |
+| Failed | failed |
+| Skipped | skipped |
+
+> For more usage examples, see the [Usage Guide](docs/usage.md).
+
+## Running Tests
+
+### Basic Execution with CLI
+
+```bash
+# Run with Qase reporter
+QASE_MODE=testops newman run ./collection.json -r qase
+
+# Run with multiple reporters
+QASE_MODE=testops newman run ./collection.json -r cli,qase
+
+# Run specific folder
+QASE_MODE=testops newman run ./collection.json -r qase --folder "API Tests"
+```
+
+### Using Data Files
+
+```bash
+# Run with JSON data file
+newman run ./collection.json -r qase -d ./data.json
+
+# Run with CSV data file
+newman run ./collection.json -r qase -d ./users.csv
+```
+
+### Using Environment Variables
+
+```bash
+# Set environment variables
+newman run ./collection.json -r qase -e ./environment.json
+
+# Override config with environment variables
+QASE_MODE=testops \
+QASE_TESTOPS_PROJECT=DEMO \
+QASE_TESTOPS_API_TOKEN=your_token \
+newman run ./collection.json -r qase
+```
+
+### Programmatic Usage
+
+```javascript
+const newman = require('newman');
+
+newman.run({
+ collection: require('./collection.json'),
+ reporters: ['qase'],
+ reporter: {
+ qase: {
+ // Reporter options can be passed here
+ // But prefer using qase.config.json for consistency
+ }
+ }
+}, function(err) {
+ if (err) { throw err; }
+ console.log('Collection run complete');
+});
+```
+
## Requirements
-We maintain the reporter on LTS versions of Node. You can find the current versions by following
-the [link](https://nodejs.org/en/about/releases/)
+- Node.js >= 14
+- Newman >= 5.3.0
+
+## Documentation
+
+| Guide | Description |
+|-------|-------------|
+| [Usage Guide](docs/usage.md) | Complete usage reference with parameters and examples |
+| [Attachments](docs/ATTACHMENTS.md) | Adding screenshots, logs, and files to test results |
+| [Steps](docs/STEPS.md) | Defining test steps for detailed reporting |
+| [Multi-Project Support](docs/MULTI_PROJECT.md) | Reporting to multiple Qase projects |
+| [Upgrade Guide](docs/UPGRADE.md) | Migration guide for breaking changes |
+| [Configuration Reference](../qase-javascript-commons/README.md) | Full configuration options |
+
+## Examples
-`newman >= 5.3.0`
+See the [examples directory](../examples/single/newman/) for complete working examples.
-
+## License
-[auth]: https://developers.qase.io/#authentication
+Apache License 2.0. See [LICENSE](LICENSE) for details.
diff --git a/qase-newman/docs/ATTACHMENTS.md b/qase-newman/docs/ATTACHMENTS.md
new file mode 100644
index 00000000..f85509fd
--- /dev/null
+++ b/qase-newman/docs/ATTACHMENTS.md
@@ -0,0 +1,131 @@
+# Attachments in Newman
+
+This guide covers attachment capabilities and limitations when using Newman with Qase reporter.
+
+---
+
+## Limitations
+
+**Newman reporter does not support programmatic attachments.** Newman runs Postman collections which do not have a mechanism to attach files to individual test results.
+
+Unlike other Qase reporters, there is no `qase.attach()` API available in Newman/Postman test scripts.
+
+---
+
+## What IS Reported
+
+While attachments cannot be added programmatically, the Newman reporter automatically captures:
+
+- **Request data** — HTTP method, URL, headers
+- **Response data** — Status code, response body, headers
+- **Test results** — Each `pm.test()` assertion result
+- **Execution metadata** — Collection name, environment, iterations
+
+These are included in the test result details sent to Qase.
+
+---
+
+## Alternatives
+
+### 1. Use Postman Console Logging
+
+Log important information to the Postman console, which can be viewed during test execution:
+
+```javascript
+pm.test('Response is successful', function() {
+ console.log('Request URL:', pm.request.url.toString());
+ console.log('Response Status:', pm.response.code);
+ console.log('Response Body:', pm.response.text());
+
+ pm.response.to.have.status(200);
+});
+```
+
+### 2. Store Data in Collection Variables
+
+Store test artifacts in collection or environment variables for later reference:
+
+```javascript
+pm.test('User creation successful', function() {
+ const response = pm.response.json();
+
+ // Store response for reference
+ pm.collectionVariables.set('lastResponse', JSON.stringify(response, null, 2));
+ pm.collectionVariables.set('createdUserId', response.id);
+
+ pm.expect(response.id).to.exist;
+});
+```
+
+### 3. Use Qase API Directly
+
+For critical attachments, use the Qase API to upload files after test execution:
+
+```bash
+# After Newman run, upload attachment via Qase API
+curl -X POST "https://api.qase.io/v1/result/{result_id}/attachment" \
+ -H "Token: your_api_token" \
+ -F "file=@screenshot.png"
+```
+
+### 4. Use Different Reporter
+
+If attachments are essential, consider using a different testing framework that supports the Qase attachment API:
+- **Playwright** — Full attachment support with `qase.attach()`
+- **WebdriverIO** — Supports screenshots and file attachments
+- **TestCafe** — Supports screenshots and file attachments
+
+---
+
+## Configuration
+
+While attachments are not supported, you can configure what request/response data is included in test results:
+
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "project": "YOUR_PROJECT_CODE",
+ "api": {
+ "token": "YOUR_API_TOKEN"
+ }
+ },
+ "debug": true
+}
+```
+
+Enable debug logging to see what data is being captured and reported.
+
+---
+
+## What About Screenshots?
+
+Newman runs headless API tests and does not interact with browsers, so screenshots are not applicable.
+
+If you need browser interaction with screenshots:
+- Use Playwright, Puppeteer, or Selenium for browser automation
+- Integrate with Qase using the corresponding reporter (playwright-qase-reporter, etc.)
+
+---
+
+## Troubleshooting
+
+### Why No Attachment Support?
+
+Newman is a command-line runner for Postman collections. Postman's test scripting API (`pm.*`) does not provide file system access or attachment capabilities for security and portability reasons.
+
+### Can I Add Attachments in Postman Desktop?
+
+Postman Desktop application is separate from Newman. Attachments added in Postman Desktop are not transferred to Newman test runs.
+
+### Can I Base64 Encode and Store?
+
+You can store base64-encoded data in variables, but this does not create visual attachments in Qase. The data would only be available as text in console output or variables.
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Steps Guide](STEPS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-newman/docs/MULTI_PROJECT.md b/qase-newman/docs/MULTI_PROJECT.md
new file mode 100644
index 00000000..ca952dba
--- /dev/null
+++ b/qase-newman/docs/MULTI_PROJECT.md
@@ -0,0 +1,178 @@
+# Multi-Project Support in Newman
+
+Qase Newman Reporter supports sending test results to multiple Qase projects simultaneously. This feature allows you to report the same test execution to different projects with different test case IDs, which is useful when:
+
+* You need to report the same test to different projects
+* Different projects track the same functionality with different test case IDs
+* You want to maintain separate test runs for different environments or teams
+
+---
+
+## Configuration
+
+For detailed configuration options, refer to the [qase-javascript-commons README](../../qase-javascript-commons/README.md#multi-project-support).
+
+### Basic Multi-Project Configuration
+
+Set `mode` to `testops_multi` in your `qase.config.json` and add the `testops_multi` section with `default_project` and `projects`.
+
+**Example configuration:**
+
+```json
+{
+ "mode": "testops_multi",
+ "testops_multi": {
+ "default_project": "PROJ1",
+ "projects": [
+ {
+ "code": "PROJ1",
+ "api": {
+ "token": "your_api_token_for_proj1"
+ }
+ },
+ {
+ "code": "PROJ2",
+ "api": {
+ "token": "your_api_token_for_proj2"
+ }
+ }
+ ]
+ }
+}
+```
+
+---
+
+## Using Comment-Based Markers in Postman Collections
+
+Newman uses comment-based markers in your Postman collection test scripts to map tests to multiple projects. Unlike other frameworks, Newman does not have a programmatic `qase.projects()` API because Newman executes Postman collection JSON files.
+
+### Multi-Project Markers
+
+Add special comments before your `pm.test()` calls to specify which projects and test case IDs to use:
+
+```javascript
+// qase PROJ1: 1
+// qase PROJ2: 2
+pm.test('Login flow', function () {
+ pm.response.to.have.status(200);
+});
+```
+
+### Multiple Test Case IDs per Project
+
+You can link a test to multiple case IDs within the same project by using comma-separated IDs:
+
+```javascript
+// qase PROJ1: 10,11
+// qase PROJ2: 20
+pm.test('Checkout process', function () {
+ pm.expect(pm.response.json()).to.have.property('orderId');
+});
+```
+
+### Single-Project Format (Legacy)
+
+You can still use the single-project format which reports to the `default_project`:
+
+```javascript
+// qase: 100
+pm.test('User registration', function () {
+ pm.response.to.have.status(201);
+});
+```
+
+**Key points:**
+
+- Single project with single ID: `// qase: 100`
+- Multi-project: `// Qase PROJ1: 1` and `// Qase PROJ2: 2` (separate comments)
+- Multiple IDs per project: `// Qase PROJ1: 10,11` (comma-separated, no spaces)
+
+Project codes (e.g. `PROJ1`, `PROJ2`) must match `testops_multi.projects[].code` in your config.
+
+---
+
+## Tests Without Project Mapping
+
+Tests that do not have `// Qase PROJECT: ids` comments are sent to the `default_project`. If they use the single-project format `// qase: id`, that ID is used for the default project.
+
+---
+
+## Important Notes
+
+1. **Project codes must match**: Codes in `// Qase PROJ1: 1` comments must match `testops_multi.projects[].code` in config.
+2. **Mode**: Set `mode` to `testops_multi` in qase.config.json.
+3. **Comment format**: Use exact format `// Qase PROJECT_CODE: id1,id2` before `pm.test()` calls.
+4. **API tokens**: Each project in `testops_multi.projects[]` can have its own API token for separate authentication.
+
+---
+
+## Examples
+
+See the [multi-project Newman example](../../examples/multiProject/newman/) for a complete runnable setup.
+
+### Complete Example
+
+Here's a complete Postman collection test script showing multi-project usage:
+
+```javascript
+// Test reported to two projects
+// qase PROJ1: 1
+// qase PROJ2: 2
+pm.test('Status code is 200', function () {
+ pm.response.to.have.status(200);
+});
+
+pm.test('Response has expected structure', function () {
+ pm.expect(pm.response.json()).to.have.property('args');
+});
+
+// Test reported to multiple cases in different projects
+// qase PROJ1: 10,11
+// qase PROJ2: 20
+pm.test('Response contains source parameter', function () {
+ pm.expect(pm.response.json().args).to.have.property('source');
+});
+
+// Test with single-project format (goes to default_project)
+// qase: 100
+pm.test('Headers are present', function () {
+ pm.response.to.have.header('Content-Type');
+});
+
+// Test without any markers (goes to default_project without case ID)
+pm.test('Response time is acceptable', function () {
+ pm.expect(pm.response.responseTime).to.be.below(1000);
+});
+```
+
+---
+
+## Troubleshooting
+
+### Results Not Appearing in All Projects
+
+* Verify `mode` is `testops_multi` (not `testops`) in qase.config.json
+* Check that project codes in comments match config codes exactly (case-sensitive)
+* Ensure each project has a valid API token with write permissions
+* Verify comments are placed immediately before `pm.test()` calls
+
+### Wrong Test Cases Linked
+
+* Verify the comment format: `// Qase PROJECT_CODE: id1,id2` (no spaces after commas)
+* Check that test case IDs exist in the respective projects
+* Enable debug logging to see how the reporter parses multi-project markers
+
+### Default Project Not Working
+
+* Ensure `default_project` is set in `testops_multi` config
+* Verify the default project code matches one of the projects in the `projects` array
+* Tests without multi-project markers will only report to the default project
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Examples](../../examples/multiProject/newman/)
diff --git a/qase-newman/docs/STEPS.md b/qase-newman/docs/STEPS.md
new file mode 100644
index 00000000..ca534b21
--- /dev/null
+++ b/qase-newman/docs/STEPS.md
@@ -0,0 +1,314 @@
+# Test Steps in Newman
+
+This guide covers test step reporting capabilities and limitations when using Newman with Qase reporter.
+
+---
+
+## Limitations
+
+**Newman reporter does not support programmatic test steps.** Postman collection test scripts (`pm.test()`) are reported as individual test results, not as steps within a test.
+
+Unlike other Qase reporters, there is no `qase.step()` API available in Newman/Postman test scripts.
+
+---
+
+## What IS Reported
+
+Each `pm.test()` block in your Postman collection is reported as a **separate test result** in Qase:
+
+```javascript
+// Each pm.test() is reported as a separate test
+pm.test('Status code is 200', function() {
+ pm.response.to.have.status(200);
+});
+
+pm.test('Response time is less than 500ms', function() {
+ pm.expect(pm.response.responseTime).to.be.below(500);
+});
+
+pm.test('Response has user data', function() {
+ const jsonData = pm.response.json();
+ pm.expect(jsonData).to.have.property('user');
+ pm.expect(jsonData.user).to.have.property('id');
+});
+```
+
+Each of these three `pm.test()` calls creates a separate test result in Qase with:
+- Test name (from the first argument to `pm.test()`)
+- Pass/fail status
+- Error message (if failed)
+- Execution time
+
+---
+
+## Organizing Tests
+
+### Use Descriptive Test Names
+
+Since each `pm.test()` is a separate result, use clear, descriptive names:
+
+```javascript
+// Good: Clear, specific test names
+pm.test('User ID is a positive integer', function() {
+ const jsonData = pm.response.json();
+ pm.expect(jsonData.userId).to.be.a('number');
+ pm.expect(jsonData.userId).to.be.above(0);
+});
+
+pm.test('User email is properly formatted', function() {
+ const jsonData = pm.response.json();
+ pm.expect(jsonData.email).to.match(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/);
+});
+
+// Avoid: Vague test names
+pm.test('Check response', function() {
+ // Multiple checks - unclear what failed if this fails
+ pm.expect(pm.response.code).to.equal(200);
+ pm.expect(pm.response.json()).to.have.property('user');
+});
+```
+
+### Group Related Tests in Folders
+
+Organize requests into folders in your Postman collection:
+
+```
+Collection: User API
+├── Folder: Authentication
+│ ├── POST /login
+│ ├── POST /logout
+│ └── POST /refresh-token
+├── Folder: User Management
+│ ├── GET /users/:id
+│ ├── PUT /users/:id
+│ └── DELETE /users/:id
+└── Folder: User Profile
+ ├── GET /profile
+ └── PUT /profile
+```
+
+Folder structure helps organize test results in Qase.
+
+---
+
+## Pre-request and Test Scripts
+
+### Pre-request Scripts
+
+Pre-request scripts run before the request is sent and can set up test data:
+
+```javascript
+// Pre-request Script
+pm.collectionVariables.set('timestamp', Date.now());
+pm.collectionVariables.set('randomEmail', `user${Math.random()}@example.com`);
+```
+
+These do not create test results but prepare data for the request.
+
+### Test Scripts
+
+Test scripts run after the response is received and create test results:
+
+```javascript
+// Test Script
+pm.test('Response status is 200', function() {
+ pm.response.to.have.status(200);
+});
+
+pm.test('Response contains user data', function() {
+ const jsonData = pm.response.json();
+ pm.expect(jsonData).to.have.property('user');
+});
+```
+
+Each `pm.test()` here creates a separate test result.
+
+---
+
+## Iteration Data
+
+When running Newman with data files, each iteration creates a separate test run:
+
+```bash
+newman run collection.json -d users.csv
+```
+
+**users.csv:**
+```csv
+userId,username
+1,john_doe
+2,jane_smith
+3,admin_user
+```
+
+Each row creates an iteration, and each `pm.test()` in that iteration is reported separately. Results include iteration data as parameters.
+
+---
+
+## Common Patterns
+
+### API Response Validation
+
+```javascript
+pm.test('GET /users/:id returns correct status', function() {
+ pm.response.to.have.status(200);
+});
+
+pm.test('Response contains required user fields', function() {
+ const user = pm.response.json();
+ pm.expect(user).to.have.property('id');
+ pm.expect(user).to.have.property('username');
+ pm.expect(user).to.have.property('email');
+});
+
+pm.test('User ID matches requested ID', function() {
+ const user = pm.response.json();
+ const requestedId = pm.collectionVariables.get('userId');
+ pm.expect(user.id).to.equal(parseInt(requestedId));
+});
+```
+
+### Error Handling Tests
+
+```javascript
+pm.test('Invalid credentials return 401', function() {
+ pm.response.to.have.status(401);
+});
+
+pm.test('Error response has message', function() {
+ const error = pm.response.json();
+ pm.expect(error).to.have.property('message');
+ pm.expect(error.message).to.include('Invalid credentials');
+});
+```
+
+### Performance Tests
+
+```javascript
+pm.test('Response time is acceptable', function() {
+ pm.expect(pm.response.responseTime).to.be.below(500);
+});
+
+pm.test('Response size is reasonable', function() {
+ pm.expect(pm.response.responseSize).to.be.below(10000); // 10KB
+});
+```
+
+---
+
+## Alternatives for Step-like Organization
+
+### 1. Use Multiple pm.test() Blocks
+
+Break complex validations into multiple `pm.test()` calls:
+
+```javascript
+// Instead of one large test
+pm.test('Complete user validation', function() {
+ const user = pm.response.json();
+ pm.expect(user.id).to.exist;
+ pm.expect(user.username).to.be.a('string');
+ pm.expect(user.email).to.match(/@/);
+ pm.expect(user.createdAt).to.exist;
+});
+
+// Use separate tests (reported as separate results)
+pm.test('User ID exists', function() {
+ const user = pm.response.json();
+ pm.expect(user.id).to.exist;
+});
+
+pm.test('Username is a string', function() {
+ const user = pm.response.json();
+ pm.expect(user.username).to.be.a('string');
+});
+
+pm.test('Email is valid format', function() {
+ const user = pm.response.json();
+ pm.expect(user.email).to.match(/@/);
+});
+
+pm.test('Created timestamp exists', function() {
+ const user = pm.response.json();
+ pm.expect(user.createdAt).to.exist;
+});
+```
+
+### 2. Chain Requests in Collection
+
+Create a sequence of requests that represent workflow steps:
+
+```
+Collection: User Registration Flow
+├── 1. POST /register (Create account)
+├── 2. GET /verify-email/:token (Verify email)
+├── 3. POST /login (First login)
+├── 4. GET /profile (Check profile)
+└── 5. PUT /profile (Update profile)
+```
+
+Each request represents a "step" in the workflow.
+
+---
+
+## Troubleshooting
+
+### Tests Not Appearing in Qase
+
+1. Verify reporter is enabled:
+ ```bash
+ QASE_MODE=testops newman run collection.json -r qase
+ ```
+2. Check that collection has `pm.test()` calls
+3. Enable debug logging:
+ ```json
+ {
+ "debug": true
+ }
+ ```
+
+### All Tests Show as One Result
+
+Ensure each validation is in a separate `pm.test()` call:
+
+```javascript
+// Incorrect: Multiple checks in one test
+pm.test('Validate response', function() {
+ pm.response.to.have.status(200);
+ pm.expect(pm.response.json()).to.have.property('user');
+ pm.expect(pm.response.json().user.id).to.exist;
+});
+
+// Correct: Separate tests
+pm.test('Status is 200', function() {
+ pm.response.to.have.status(200);
+});
+
+pm.test('Response has user property', function() {
+ pm.expect(pm.response.json()).to.have.property('user');
+});
+
+pm.test('User has ID', function() {
+ pm.expect(pm.response.json().user.id).to.exist;
+});
+```
+
+### Test Names Are Generic
+
+Use descriptive test names that indicate what's being validated:
+
+```javascript
+// Avoid
+pm.test('Test 1', function() { /* ... */ });
+
+// Good
+pm.test('User ID is returned in response', function() { /* ... */ });
+```
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Attachments Guide](ATTACHMENTS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-newman/docs/UPGRADE.md b/qase-newman/docs/UPGRADE.md
new file mode 100644
index 00000000..ed28783d
--- /dev/null
+++ b/qase-newman/docs/UPGRADE.md
@@ -0,0 +1,346 @@
+# Upgrade Guide: Newman Reporter
+
+This guide covers migration steps between major versions of the Qase Newman Reporter.
+
+---
+
+## Version History
+
+| Version | Release Date | Node.js Support | Key Changes |
+|---------|--------------|-----------------|-------------|
+| 2.2.0 | January 2026 | >= 14 | Current stable release with improved collection parsing |
+| 2.1.0 | December 2025 | >= 14 | Enhanced test name extraction and metadata handling |
+| 2.0.0 | August 2025 | >= 14 | Complete rewrite with new architecture |
+
+---
+
+## Upgrading to 2.x
+
+### Breaking Changes
+
+The newman-reporter-qase started with the v2.x architecture, leveraging the unified qase-javascript-commons library for consistent reporting across all test frameworks. If you are using v2.x, you are already on the latest architecture.
+
+**No migration from a previous major version is required for newman-reporter-qase.**
+
+### Current Version Features
+
+Version 2.2.0 includes:
+
+- Full support for Qase TestOps API with batch result upload
+- Comment-based test case linking in Postman collection scripts
+- Automatic test name extraction from Postman test names
+- Request/response data capture and reporting
+- Multi-project support for reporting to multiple Qase projects
+- Flexible configuration via `qase.config.json`, CLI flags, or environment variables
+
+---
+
+## Configuration
+
+### Current Format (v2.x)
+
+Configuration uses the modern qase-javascript-commons format:
+
+**qase.config.json:**
+
+```json
+{
+ "mode": "testops",
+ "debug": false,
+ "testops": {
+ "api": {
+ "token": "api_token_here"
+ },
+ "project": "DEMO",
+ "run": {
+ "title": "Newman Automated Run",
+ "description": "API test run from CI/CD pipeline",
+ "complete": true
+ },
+ "batch": {
+ "size": 100
+ }
+ }
+}
+```
+
+**Environment Variables:**
+
+```bash
+export QASE_MODE=testops
+export QASE_TESTOPS_API_TOKEN=your_api_token
+export QASE_TESTOPS_PROJECT=DEMO
+```
+
+**Command Line:**
+
+```bash
+npx newman run collection.json -r qase \
+ --reporter-qase-mode testops \
+ --reporter-qase-testops-api-token your_token \
+ --reporter-qase-testops-project DEMO
+```
+
+---
+
+## Usage Pattern
+
+### Test Case Linking via Comments
+
+Newman uses comment-based annotations in Postman collection test scripts:
+
+**Postman collection test script:**
+
+```javascript
+// qase: 1
+pm.test("Status code is 200", function () {
+ pm.response.to.have.status(200);
+});
+
+// qase: 2,3
+pm.test("Response has correct structure", function () {
+ const jsonData = pm.response.json();
+ pm.expect(jsonData).to.have.property('id');
+ pm.expect(jsonData).to.have.property('name');
+});
+```
+
+**Comment format:**
+
+- Single test case: `// qase: 1`
+- Multiple test cases: `// qase: 1,2,3`
+- Must be on the line directly before `pm.test()`
+
+**No programmatic import** - Newman reporter works with existing Postman collection syntax.
+
+---
+
+## API Limitations
+
+Newman reporter has a unique constraint compared to other Qase reporters:
+
+| Feature | Other Reporters | Newman |
+|---------|----------------|--------|
+| Test case ID linking | `qase(id, 'name')` wrapper | `// qase: id` comment |
+| Metadata (fields) | `qase.fields({...})` | Not supported |
+| Steps | `qase.step()` or native steps | Not supported |
+| Attachments | `qase.attach()` | Not supported |
+| Titles | `qase.title()` | Auto-extracted from `pm.test()` |
+| Suites | `qase.suite()` | Auto-extracted from folder structure |
+
+**Why the limitations?**
+
+Newman runs Postman collections without a programmatic API in test scripts. The reporter can only access what Newman provides: test names, pass/fail status, request/response data, and collection structure.
+
+**What IS captured:**
+
+- Test name (from `pm.test()` name)
+- Test status (passed/failed)
+- Request URL, method, headers, body
+- Response status, headers, body
+- Execution time
+- Suite hierarchy (from collection folder structure)
+
+---
+
+## Running Tests
+
+### Command Line Usage
+
+**Basic:**
+
+```bash
+npx newman run collection.json -r qase
+```
+
+**With Configuration:**
+
+```bash
+QASE_MODE=testops \
+QASE_TESTOPS_API_TOKEN=your_token \
+QASE_TESTOPS_PROJECT=DEMO \
+npx newman run collection.json -r qase
+```
+
+**With CLI Options:**
+
+```bash
+npx newman run collection.json -r qase \
+ --reporter-qase-mode testops \
+ --reporter-qase-testops-api-token your_token \
+ --reporter-qase-testops-project DEMO \
+ --reporter-qase-testops-run-title "API Tests - Production" \
+ --reporter-qase-testops-run-complete true
+```
+
+**Combined with other reporters:**
+
+```bash
+npx newman run collection.json -r cli,qase
+```
+
+---
+
+## Collection Structure
+
+### Example Postman Collection
+
+**Folder structure provides suite hierarchy:**
+
+```
+Collection: API Tests
+├── Folder: Authentication
+│ ├── Request: Login (with test: qase: 1)
+│ └── Request: Logout (with test: qase: 2)
+└── Folder: Users
+ ├── Request: Get User (with test: qase: 3)
+ └── Request: Create User (with test: qase: 4)
+```
+
+**Reported to Qase as:**
+
+- Suite: "API Tests / Authentication"
+ - Test case 1: "Login - Status code is 200"
+ - Test case 2: "Logout - Status code is 200"
+- Suite: "API Tests / Users"
+ - Test case 3: "Get User - Response has user data"
+ - Test case 4: "Create User - User created successfully"
+
+---
+
+## Compatibility Notes
+
+### Node.js Version Support
+
+- **Current (2.2.0):** Node.js >= 14
+
+### Newman Version Support
+
+- **Current (2.2.0):** Newman >= 5.3.0
+- Tested with Newman 6.x
+
+### Framework Compatibility
+
+- Works with all Postman collection formats
+- Compatible with Newman CLI and programmatic usage
+- Supports environment and global variables
+- Works with Newman's HTML and JSON reporters (combined usage)
+
+---
+
+## Troubleshooting
+
+### Common Issues
+
+#### Issue: Reporter not running
+
+**Solution:** Ensure you're using the `-r Qase` flag:
+
+```bash
+# Correct
+npx newman run collection.json -r qase
+
+# Incorrect - missing reporter flag
+npx newman run collection.json
+```
+
+#### Issue: Test case IDs not recognized
+
+**Solution:** Check your comment syntax in Postman test scripts:
+
+```javascript
+// Correct
+// qase: 1
+pm.test("Test name", function () { /* ... */ });
+
+// Also correct - multiple IDs
+// qase: 1,2,3
+pm.test("Test name", function () { /* ... */ });
+
+// Incorrect - missing space after colon
+// qase:1
+pm.test("Test name", function () { /* ... */ });
+
+// Incorrect - not directly before pm.test()
+// qase: 1
+
+pm.test("Test name", function () { /* ... */ });
+```
+
+#### Issue: Tests not reported to Qase
+
+**Solution:** Verify configuration:
+
+1. Check that `QASE_MODE=testops` is set
+2. Verify API token: `QASE_TESTOPS_API_TOKEN=your_token`
+3. Check project code: `QASE_TESTOPS_PROJECT=YOUR_PROJECT_CODE`
+4. Ensure reporter is specified: `-r Qase`
+
+#### Issue: Cannot add custom fields or steps
+
+**Solution:** This is expected behavior. Newman reporter does not support:
+
+- Custom fields (`qase.fields()`)
+- Custom steps (`qase.step()`)
+- Custom attachments (`qase.attach()`)
+
+These features require programmatic API access that isn't available in Postman collection scripts. Use request/response data capture instead, which is automatically included in test results.
+
+#### Issue: Configuration not recognized
+
+**Solution:** Try different configuration methods:
+
+**Method 1: Environment variables**
+```bash
+export QASE_MODE=testops
+export QASE_TESTOPS_API_TOKEN=your_token
+export QASE_TESTOPS_PROJECT=DEMO
+npx newman run collection.json -r qase
+```
+
+**Method 2: CLI options**
+```bash
+npx newman run collection.json -r qase \
+ --reporter-qase-mode testops \
+ --reporter-qase-testops-api-token your_token \
+ --reporter-qase-testops-project DEMO
+```
+
+**Method 3: qase.config.json**
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "api": { "token": "your_token" },
+ "project": "DEMO"
+ }
+}
+```
+
+---
+
+## Getting Help
+
+If you encounter issues:
+
+1. Check the [GitHub Issues](https://github.com/qase-tms/qase-javascript/issues)
+2. Review the CHANGELOG
+3. Open a new issue with:
+ - Current version (2.2.0)
+ - Newman version
+ - Error messages
+ - Configuration method used
+ - Example collection (without sensitive data)
+ - Command used to run Newman
+ - Steps to reproduce
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Multi-Project Support](MULTI_PROJECT.md)
+- CHANGELOG
+- [Newman Documentation](https://learning.postman.com/docs/collections/using-newman-cli/command-line-integration-with-newman/)
+- [Postman Documentation](https://learning.postman.com/docs/getting-started/introduction/)
diff --git a/qase-newman/docs/usage.md b/qase-newman/docs/usage.md
index 715eb02e..066a0bc3 100644
--- a/qase-newman/docs/usage.md
+++ b/qase-newman/docs/usage.md
@@ -1,12 +1,87 @@
-# How to Use Parameters from Data Files in Newman
+# Qase Integration in Newman
-Newman allows you to leverage parameters from data files to make your API tests more dynamic and efficient. By utilizing
-the `--data` or `-d` option when running a collection, you can feed your tests with various input sets. The data files
-can be formatted as either JSON or CSV.
+This guide provides comprehensive instructions for integrating Qase with Newman (Postman CLI runner).
+
+> **Configuration:** For complete configuration reference including all available options, environment variables, and examples, see the [qase-javascript-commons README](../../qase-javascript-commons/README.md).
+
+---
+
+## Table of Contents
+
+- [Adding QaseID](#adding-qaseid)
+- [Working with Parameters](#working-with-parameters)
+- [Test Result Mapping](#test-result-mapping)
+- [Running Tests](#running-tests)
+- [Troubleshooting](#troubleshooting)
+- [Integration Patterns](#integration-patterns)
+- [Common Use Cases](#common-use-cases)
+
+- [Limitations](#limitations)
+- [See Also](#see-also)
+---
+
+## Adding QaseID
+
+Link your Newman/Postman tests to existing test cases in Qase by specifying the test case ID using special comments in your test scripts.
+
+> **Important:** Newman uses comment-based annotations, not programmatic imports like other frameworks. The comment must be placed immediately before the `pm.test()` call.
+
+### Single ID
+
+```javascript
+// qase: 222
+pm.test('Status code is 200', function() {
+ pm.response.to.have.status(200);
+});
+```
+
+### Multiple IDs
+
+Newman supports several formats for multiple IDs:
+
+```javascript
+// Qase: 1, 2, 3
+pm.test('Verify response structure', function() {
+ pm.expect(pm.response.json()).to.have.property('data');
+});
+
+// Alternative space-separated format
+// qase: 4 5 6 14
+pm.test('Check multiple conditions', function() {
+ pm.response.to.be.ok;
+});
+```
+
+### Supported Comment Formats
+
+All of these formats are valid:
+
+- `// qase: 10` (lowercase, single ID)
+- `// Qase: 1, 2, 3` (capitalized, comma-separated)
+- `// qase: 4 5 6` (space-separated)
+- `//qase: 7` (no space after //)
+
+---
+
+## Working with Parameters
+
+Newman supports parameterized tests when using data files (`-d` or `--data` option). You can control which parameters are reported to Qase using special comments.
+
+### Specifying Parameters Explicitly
+
+Add a comment to specify which parameters to report:
+
+```javascript
+// qase.parameters: userId, user.name
+pm.test('User ID is correct', function() {
+ var jsonData = pm.response.json();
+ pm.expect(jsonData.userId).to.eql(pm.iterationData.get('userid'));
+});
+```
### Example Data File
-Consider the following `data.json` file, which contains user data structured as complex objects:
+Create a `data.json` file:
```json
[
@@ -27,81 +102,50 @@ Consider the following `data.json` file, which contains user data structured as
]
```
-### Example Tests
+### Run with Data File
-Below are example tests that utilize the data parameters defined in the data file:
-
-```javascript
-// qase.parameters: userId, user.name
-pm.test("Status code is 201", function() {
- pm.response.to.have.status(201);
-});
-
-// qase.parameters: userId
-pm.test("Response has correct userId", function() {
- var jsonData = pm.response.json();
- pm.expect(jsonData.userId).to.eql(pm.iterationData.get("userid"));
-});
-
-pm.test("Response has correct name", function() {
- var jsonData = pm.response.json();
- pm.expect(jsonData.user.name).to.eql(pm.iterationData.get("user.name"));
-});
+```bash
+newman run collection.json -r qase -d data.json
```
-You also can specify parameters on collection or folder level. In this case, all tests in collection or folder will have
-these parameters. If test has own parameters, they will be merged with collection or folder parameters.
+### Collection-Level Parameters
+
+You can also specify parameters at the collection or folder level. All tests within will inherit these parameters:
```json
{
"info": {
- "_postman_id": "collection_id",
- "name": "Collection Name",
- "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+ "name": "API Tests"
},
"item": [
{
- "name": "Folder Name",
+ "name": "User Folder",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ "// qase.parameters: userId, user.name"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"item": [
{
- "name": "Test Name",
+ "name": "Get User",
"event": [
{
"listen": "test",
"script": {
- "type": "text/javascript",
"exec": [
- "pm.test('Status code is 200', function () {",
- " pm.response.to.have.status(200);",
+ "pm.test('User found', function() {",
+ " pm.response.to.have.status(200);",
"})"
]
}
}
- ],
- "request": {
- "method": "GET",
- "header": [],
- "url": {
- "raw": "https://api.example.com",
- "host": [
- "api",
- "example",
- "com"
- ]
- }
- },
- "response": []
- }
- ],
- "event": [
- {
- "listen": "test",
- "script": {
- "exec": [
- "// qase.parameters: userId, user.name"
- ],
- "type": "text/javascript"
- }
+ ]
}
]
}
@@ -109,32 +153,583 @@ these parameters. If test has own parameters, they will be merged with collectio
}
```
-### Expected Behavior
-
-When you run the tests, the following behavior is expected:
+### Auto-Collect All Parameters
-- In the **`Status code is 201`** test, both `userId` and `user.name` will be passed as parameters.
-- In the **`Response has correct userId`** test, only the `userId` parameter will be passed.
-- In the **`Response has correct name`** test, by default, test will not have any parameters passed. But you can enable
- specific option in config file to pass all parameters from data file if test have not commented `qase.parameters`
- line.
+To automatically report all parameters from data files without manually specifying them, enable the `autoCollectParams` option:
- ```json
- {
- "debug": true,
- "testops": {
- "api": {
- "token": "api_key"
- },
- "project": "project_code",
- "run": {
- "complete": true
- }
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "api": {
+ "token": "api_token"
},
- "framework": {
- "newman": {
- "autoCollectParams": true
- }
+ "project": "project_code"
+ },
+ "framework": {
+ "newman": {
+ "autoCollectParams": true
+ }
+ }
+}
+```
+
+With this enabled, all tests will automatically report all available parameters from the data file.
+
+### Nested Parameter Access
+
+You can access nested object properties using dot notation:
+
+```javascript
+// qase.parameters: user.name, user.age, address.city
+pm.test('User data is correct', function() {
+ pm.expect(pm.iterationData.get('user.name')).to.eql('John');
+});
+```
+
+---
+
+## Test Result Mapping
+
+Newman test results are mapped to Qase statuses:
+
+| Newman Result | Qase Status | Description |
+|---------------|-------------|-------------|
+| Passed | passed | Test assertion succeeded |
+| Failed | failed | Test assertion failed |
+| Skipped | skipped | Test was not executed |
+
+---
+
+## Running Tests
+
+### Basic Execution
+
+```bash
+# Run collection with Qase reporter
+QASE_MODE=testops newman run ./collection.json -r qase
+
+# Run specific folder
+QASE_MODE=testops newman run ./collection.json -r qase --folder "API Tests"
+```
+
+### Multiple Reporters
+
+```bash
+# Combine CLI output with Qase reporting
+newman run ./collection.json -r cli,qase
+
+# JSON output and Qase
+newman run ./collection.json -r json,qase
+```
+
+### With Environment
+
+```bash
+# Use Postman environment file
+newman run ./collection.json -r qase -e ./production.postman_environment.json
+
+# Override environment variables
+QASE_MODE=testops \
+QASE_TESTOPS_PROJECT=DEMO \
+newman run ./collection.json -r qase
+```
+
+### With Data Files
+
+```bash
+# JSON data file
+newman run ./collection.json -r qase -d ./users.json
+
+# CSV data file
+newman run ./collection.json -r qase -d ./test-data.csv
+```
+
+### Programmatic Usage
+
+```javascript
+const newman = require('newman');
+
+newman.run({
+ collection: require('./collection.json'),
+ environment: require('./environment.json'),
+ reporters: ['qase'],
+ iterationData: './data.json',
+ reporter: {
+ qase: {
+ // Reporter-specific options (optional)
+ // Prefer using qase.config.json for consistency
+ }
+ }
+}, function(err) {
+ if (err) {
+ throw err;
+ }
+ console.log('Collection run complete');
+});
+```
+
+### CI/CD Integration
+
+```bash
+#!/bin/bash
+# ci-run.sh
+
+# Exit on error
+set -e
+
+# Load API token from CI environment
+export QASE_MODE=testops
+export QASE_TESTOPS_PROJECT=DEMO
+export QASE_TESTOPS_API_TOKEN="${CI_QASE_TOKEN}"
+
+# Run Newman with Qase reporter
+newman run ./api-tests.json \
+ -r qase \
+ -e ./ci-environment.json \
+ --bail \
+ --color off
+```
+
+---
+
+## Troubleshooting
+
+### Tests Not Appearing in Qase
+
+**Problem:** Tests run successfully but don't appear in Qase TestOps.
+
+**Solutions:**
+
+1. **Verify mode is set to TestOps:**
+ ```bash
+ # Check environment variable
+ echo $QASE_MODE # Should output: testops
+
+ # Or check qase.config.json
+ cat qase.config.json | grep mode
+ ```
+
+2. **Verify API token is correct:**
+ ```bash
+ # Test token is valid
+ curl -H "Token: YOUR_API_TOKEN" https://api.qase.io/v1/project
+ ```
+
+3. **Check project code:**
+ - Ensure project code in config matches your Qase project
+ - Project codes are case-sensitive
+
+4. **Enable debug logging:**
+ ```json
+ {
+ "debug": true,
+ "TestOps": {
+ "api": { "token": "..." },
+ "project": "DEMO"
+ }
+ }
+ ```
+
+### QaseID Comments Not Working
+
+**Problem:** Tests with `// qase: N` comments not linking to test cases.
+
+**Solutions:**
+
+1. **Verify comment format:**
+ ```javascript
+ // Correct:
+ // qase: 1
+ pm.test('Test name', function() { ... });
+
+ // Incorrect (comment too far from test):
+ // qase: 1
+
+ pm.test('Test name', function() { ... });
+ ```
+
+2. **Check test case ID exists:**
+ - Verify the test case ID exists in your Qase project
+ - IDs are specific to each project
+
+3. **Check for typos:**
+ ```javascript
+ // Correct:
+ // qase: 1
+
+ // Incorrect:
+ // quase: 1
+ // qse: 1
+ ```
+
+### Parameters Not Reported
+
+**Problem:** Data file parameters not appearing in Qase results.
+
+**Solutions:**
+
+1. **Add parameter comment:**
+ ```javascript
+ // qase.parameters: userId, user.name
+ pm.test('Test with params', function() { ... });
+ ```
+
+2. **Or enable auto-collection:**
+ ```json
+ {
+ "framework": {
+ "newman": {
+ "autoCollectParams": true
+ }
+ }
+ }
+ ```
+
+3. **Verify data file format:**
+ ```json
+ // Correct JSON format:
+ [
+ { "userId": 1, "name": "John" },
+ { "userId": 2, "name": "Jane" }
+ ]
+ ```
+
+4. **Check parameter names match:**
+ ```javascript
+ // Comment uses: userId
+ // qase.parameters: userId
+
+ // Data file must have:
+ { "userId": 1 } // Correct
+ { "userid": 1 } // Wrong case
+ ```
+
+### Reporter Not Found
+
+**Problem:** `Error: Reporter "Qase" not found`.
+
+**Solutions:**
+
+1. **Install the reporter:**
+ ```bash
+ npm install --save-dev newman-reporter-Qase
+ ```
+
+2. **Verify installation:**
+ ```bash
+ npm list newman-reporter-Qase
+ ```
+
+3. **Check Node.js and Newman versions:**
+ ```bash
+ node --version # Should be >= 14
+ newman --version # Should be >= 5.3.0
+ ```
+
+### Collection Format Issues
+
+**Problem:** Collection file parsing errors.
+
+**Solutions:**
+
+1. **Validate collection format:**
+ - Export collection from Postman (v2.1 format)
+ - Use Postman Collection SDK to validate
+
+2. **Check JSON syntax:**
+ ```bash
+ # Validate JSON
+ cat collection.json | jq .
+ ```
+
+3. **Re-export from Postman:**
+ - Open in Postman → Collection Settings → Export → v2.1
+
+---
+
+## Integration Patterns
+
+### Pattern 1: API Smoke Tests
+
+```javascript
+// Collection: API Smoke Tests
+// Folder: Critical Endpoints
+
+// qase: 101
+// qase.parameters: environment
+pm.test('Health check endpoint responds', function() {
+ pm.response.to.have.status(200);
+ pm.expect(pm.response.json().status).to.eql('healthy');
+});
+
+// qase: 102
+pm.test('Authentication endpoint is accessible', function() {
+ pm.response.to.have.status(200);
+});
+```
+
+### Pattern 2: Parameterized User Tests
+
+**Data file (users.json):**
+```json
+[
+ { "role": "admin", "userId": 1, "expectedStatus": 200 },
+ { "role": "user", "userId": 2, "expectedStatus": 200 },
+ { "role": "guest", "userId": 3, "expectedStatus": 403 }
+]
+```
+
+**Test script:**
+```javascript
+// qase: 201
+// qase.parameters: role, userId, expectedStatus
+pm.test('User access based on role', function() {
+ pm.expect(pm.response.code).to.eql(pm.iterationData.get('expectedStatus'));
+});
+```
+
+### Pattern 3: Environment-Specific Tests
+
+**Collection-level script:**
+```javascript
+// qase.parameters: environment
+// Set in pre-request or test event at collection level
+```
+
+**Individual test:**
+```javascript
+// qase: 301
+pm.test('API responds correctly in ' + pm.environment.get('env'), function() {
+ pm.response.to.be.ok;
+ pm.expect(pm.response.json()).to.have.property('data');
+});
+```
+
+### Pattern 4: Chained Requests with Shared Context
+
+```javascript
+// Request 1: Login
+// qase: 401
+pm.test('Login successful', function() {
+ pm.response.to.have.status(200);
+ var token = pm.response.json().token;
+ pm.environment.set('authToken', token);
+});
+
+// Request 2: Get User (uses token from Request 1)
+// qase: 402
+pm.test('Fetch user data with token', function() {
+ pm.response.to.have.status(200);
+ pm.expect(pm.response.json()).to.have.property('user');
+});
+```
+
+---
+
+## Common Use Cases
+
+### Use Case 1: Report API Test Collection
+
+```bash
+# Run entire collection
+QASE_MODE=testops newman run ./api-tests.json -r qase
+```
+
+### Use Case 2: Run in CI/CD Pipeline
+
+**.gitlab-ci.yml:**
+```yaml
+newman-tests:
+ stage: test
+ image: postman/newman:latest
+ script:
+ - npm install -g newman-reporter-qase
+ - newman run collection.json -r qase
+ variables:
+ QASE_MODE: testops
+ QASE_TESTOPS_PROJECT: DEMO
+ QASE_TESTOPS_API_TOKEN: $CI_QASE_TOKEN
+```
+
+### Use Case 3: Test with Multiple Environments
+
+```bash
+# Production
+QASE_MODE=testops newman run collection.json -r qase -e prod.json
+
+# Staging
+QASE_MODE=testops newman run collection.json -r qase -e staging.json
+```
+
+### Use Case 4: Filter and Report Specific Folder
+
+```bash
+# Only run tests in "Critical" folder
+newman run collection.json -r qase --folder "Critical"
+```
+
+### Use Case 5: Programmatic Run with Custom Options
+
+```javascript
+const newman = require('newman');
+const fs = require('fs');
+
+const collections = ['auth.json', 'users.json', 'products.json'];
+
+collections.forEach(collectionFile => {
+ newman.run({
+ collection: require(`./${collectionFile}`),
+ reporters: ['qase'],
+ environment: require('./environment.json')
+ }, (err, summary) => {
+ if (err) {
+ console.error(`Failed: ${collectionFile}`);
+ throw err;
+ }
+ console.log(`Completed: ${collectionFile}`);
+ });
+});
+```
+
+### Use Case 6: Data-Driven Testing with CSV
+
+**test-data.csv:**
+```csv
+userId,username,expectedStatus
+1,admin,200
+2,user,200
+3,guest,403
+```
+
+**Run:**
+```bash
+newman run collection.json -r qase -d test-data.csv
+```
+
+**Test script:**
+```javascript
+// qase: 601
+// qase.parameters: userId, username, expectedStatus
+pm.test('Access control for ' + pm.iterationData.get('username'), function() {
+ pm.expect(pm.response.code).to.eql(
+ parseInt(pm.iterationData.get('expectedStatus'))
+ );
+});
+```
+
+### Use Case 7: Parallel Execution with Different Data
+
+```bash
+# Terminal 1: Run with dataset A
+newman run collection.json -r qase -d dataset-a.json
+
+# Terminal 2: Run with dataset B
+newman run collection.json -r qase -d dataset-b.json
+```
+
+### Use Case 8: Integration with Pre-Request Scripts
+
+```javascript
+// Pre-request script (collection or folder level)
+// Set dynamic parameters
+pm.variables.set('timestamp', new Date().toISOString());
+pm.variables.set('requestId', Date.now());
+
+// Test script
+// qase: 801
+// qase.parameters: timestamp, requestId
+pm.test('Request tracking', function() {
+ pm.response.to.have.status(200);
+});
+```
+
+### Use Case 9: Custom Test Run Titles
+
+**qase.config.json:**
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "api": { "token": "..." },
+ "project": "DEMO",
+ "run": {
+ "title": "API Regression - {{date}}",
+ "description": "Automated API tests"
}
}
- ```
+}
+```
+
+### Use Case 10: Conditional Testing Based on Environment
+
+```javascript
+// qase: 901
+pm.test('Feature X available in production', function() {
+ const env = pm.environment.get('environment');
+
+ if (env === 'production') {
+ pm.expect(pm.response.json().features).to.include('featureX');
+ } else {
+ pm.expect(pm.response.json().features).to.not.include('featureX');
+ }
+});
+```
+
+---
+
+## Limitations
+
+Newman reporter has some limitations compared to programmatic reporters:
+
+### No Steps Support
+
+Newman does not support test steps. All test assertions are reported at the test level.
+
+**Workaround:** Use multiple `pm.test()` calls for granular reporting:
+
+```javascript
+// Instead of steps, use separate tests
+// qase: 1
+pm.test('Step 1: Response is successful', function() {
+ pm.response.to.be.ok;
+});
+
+// qase: 1
+pm.test('Step 2: Response has data', function() {
+ pm.expect(pm.response.json()).to.have.property('data');
+});
+```
+
+### No Suite Organization
+
+Newman does not support programmatic suite assignment. Test organization comes from Postman collection structure (folders).
+
+**Workaround:** Use Postman folders to organize tests into logical suites.
+
+### No Custom Fields
+
+Newman does not support adding custom fields (severity, priority, etc.) via comments.
+
+**Workaround:** Set fields manually in Qase TestOps UI after test case creation.
+
+### No Attachments
+
+Newman reporter does not support attaching files or screenshots to test results.
+
+**Workaround:** Use Postman's native logging and save artifacts separately.
+
+### No Ignore Functionality
+
+Individual tests cannot be excluded from reporting.
+
+**Workaround:** Use Newman's `--folder` flag to run only specific folders.
+
+---
+
+## See Also
+
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Newman Documentation](https://www.npmjs.com/package/newman)
+- [Postman Collection Format](https://schema.postman.com/)
+- [Example Collections](../../examples/single/newman/)
diff --git a/qase-playwright/README.md b/qase-playwright/README.md
index 26424fac..a185bd9e 100644
--- a/qase-playwright/README.md
+++ b/qase-playwright/README.md
@@ -1,246 +1,300 @@
-# Qase TMS Playwright reporter
+# [Qase TestOps](https://qase.io) Playwright Reporter
-Qase Playwright reporter sends test results and metadata to Qase.io.
-It can work in different test automation scenarios:
+[](https://www.apache.org/licenses/LICENSE-2.0)
+[](https://www.npmjs.com/package/playwright-qase-reporter)
-* Create new test cases in Qase from existing autotests.
-* Report Playwright test results to existing test cases in Qase.
-* Update existing cases with metadata, such as parameters and fields.
+Qase Playwright Reporter enables seamless integration between your Playwright tests and [Qase TestOps](https://qase.io), providing automatic test result reporting, test case management, and comprehensive test analytics.
-To install the latest version, run:
+## Features
+
+- Link automated tests to Qase test cases by ID
+- Auto-create test cases from your test code
+- Report test results with rich metadata (fields, attachments, steps)
+- Support for parameterized tests
+- Multi-project reporting support
+- Flexible configuration (file, environment variables, Playwright config)
+- **Unique to Playwright:** Multiple API patterns (wrapper function, method-based, annotations)
+
+## Installation
```sh
-npm install -D playwright-qase-reporter
+npm install --save-dev playwright-qase-reporter
+```
+
+## Quick Start
+
+**1. Create `qase.config.json` in your project root:**
+
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "project": "YOUR_PROJECT_CODE",
+ "api": {
+ "token": "YOUR_API_TOKEN"
+ }
+ }
+}
```
-
-
+**2. Add Qase ID to your test:**
-# Contents
+Playwright offers **two ways** to link tests with Qase test cases:
-- [Qase TMS Playwright reporter](#qase-tms-playwright-reporter)
-- [Contents](#contents)
- - [Getting started](#getting-started)
- - [Updating from v1](#updating-from-v1)
- - [Example of usage](#example-of-usage)
- - [Configuration](#configuration)
- - [Requirements](#requirements)
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
-
+// Option 1: Wrapper function (similar to Jest)
+test(qase(1, 'User can login with valid credentials'), async ({ page }) => {
+ await page.goto('https://example.com');
+ expect(await page.title()).toBe('Example Domain');
+});
-## Getting started
+// Option 2: Method-based (Playwright's unique approach)
+test('User can login', async ({ page }) => {
+ qase.id(1);
+ qase.title('User can login with valid credentials');
+ await page.goto('https://example.com');
+ expect(await page.title()).toBe('Example Domain');
+});
+```
-To report your tests results to Qase, install `playwright-qase-reporter`,
-and add a reporter config in the `playwright.config.ts` file.
-A minimal configuration needs just two things:
+**3. Configure Playwright reporter in `playwright.config.ts`:**
-* Qase project code, for example, in https://app.qase.io/project/DEMO the code is `DEMO`.
-* Qase API token, created on the [Apps page](https://app.qase.io/apps?app=playwright-reporter).
+```typescript
+import { defineConfig } from '@playwright/test';
-```js
-const config: PlaywrightTestConfig = {
- // ...
+export default defineConfig({
reporter: [
+ ['list'],
[
'playwright-qase-reporter',
{
+ mode: 'testops',
testops: {
api: {
- token: 'api_token',
+ token: process.env.QASE_API_TOKEN,
},
- project: 'project_code',
+ project: 'YOUR_PROJECT_CODE',
},
},
],
],
- // ...
-};
-module.exports = config;
+});
```
-Now run the tests as usual.
-Test results will be reported to a new test run in Qase:
-
-```console
-$ npx playwright test
-Running 5 tests using 1 worker
-...
-...
-...
-qase: 5 results sent to Qase
-qase: run 1 completed
-qase: Test run link: https://app.qase.io/run/DEMO/dashboard/1
+**4. Run your tests:**
+
+```sh
+npx playwright test
```
-## Updating from v1
+## Configuration
-To update a test project using qase-playwright-reporter@v1 to version 2:
+The reporter is configured via (in order of priority):
+
+1. **playwright.config.ts** (Playwright-specific, highest priority)
+2. **Environment variables** (`QASE_*`)
+3. **Config file** (`qase.config.json`)
+
+### Minimal Configuration
+
+| Option | Environment Variable | Description |
+|--------|---------------------|-------------|
+| `mode` | `QASE_MODE` | Set to `testops` to enable reporting |
+| `testops.project` | `QASE_TESTOPS_PROJECT` | Your Qase project code |
+| `testops.api.token` | `QASE_TESTOPS_API_TOKEN` | Your Qase API token |
+
+### Example `qase.config.json`
+
+```json
+{
+ "mode": "testops",
+ "fallback": "report",
+ "testops": {
+ "project": "YOUR_PROJECT_CODE",
+ "api": {
+ "token": "YOUR_API_TOKEN"
+ },
+ "run": {
+ "title": "Playwright Automated Run"
+ },
+ "batch": {
+ "size": 100
+ }
+ },
+ "report": {
+ "driver": "local",
+ "connection": {
+ "local": {
+ "path": "./build/qase-report",
+ "format": "json"
+ }
+ }
+ }
+}
+```
-1. Change the import paths:
+### Example `playwright.config.ts`
- ```diff
- - import { qase } from 'playwright-qase-reporter/dist/playwright'
- + import { qase } from 'playwright-qase-reporter'
- ```
+```typescript
+import { defineConfig } from '@playwright/test';
-2. Update reporter configuration in `playwright.config.js` and/or environment variables —
- see the [configuration reference](#configuration) below.
+export default defineConfig({
+ reporter: [
+ ['list'],
+ [
+ 'playwright-qase-reporter',
+ {
+ mode: 'testops',
+ debug: false,
+ testops: {
+ api: {
+ token: process.env.QASE_API_TOKEN,
+ },
+ project: 'YOUR_PROJECT_CODE',
+ uploadAttachments: true,
+ run: {
+ title: 'Automated Playwright Run',
+ description: 'Nightly regression tests',
+ complete: true,
+ },
+ batch: {
+ size: 100,
+ },
+ },
+ framework: {
+ browser: {
+ addAsParameter: true,
+ parameterName: 'Browser',
+ },
+ markAsFlaky: true,
+ },
+ },
+ ],
+ ],
+});
+```
-The previous test annotation syntax is still supported, so there is no need to rewrite the tests.
-However, check out the docs for the new, more flexible and powerful syntax.
+> **Full configuration reference:** See [qase-javascript-commons](../qase-javascript-commons/README.md) for all available options including logging, status mapping, execution plans, and more.
-## Example of usage
+## Usage
-The Playwright reporter has the ability to auto-generate test cases
-and suites from your test data.
+### Link Tests with Test Cases
-But if necessary, you can independently register the ID of already
-existing test cases from TMS before the executing tests. For example:
+Playwright provides **multiple patterns** for linking tests. Choose the one that fits your style:
```typescript
+import { test, expect } from '@playwright/test';
import { qase } from 'playwright-qase-reporter';
-describe('Test suite', () => {
- test(qase(2, 'Test with Qase ID'), () => {
- expect(true).toBe(true);
- });
-
- test('Simple test', () => {
- qase.title('Example of simple test');
- expect(true).toBe(true);
- });
+// Pattern 1: Wrapper function with single ID
+test(qase(1, 'Test name'), async ({ page }) => {
+ expect(true).toBe(true);
+});
- test('Test with annotated fields', () => {
- qase.fields({ 'severity': 'high', 'priority': 'medium' });
- expect(true).toBe(true);
- });
-
- test('Running, but not reported to Qase', () => {
- qase.ignore();
- expect(true).toBe(true);
- });
+// Pattern 2: Wrapper function with multiple IDs
+test(qase([1, 2, 3], 'Test covering multiple cases'), async ({ page }) => {
+ expect(true).toBe(true);
+});
- test('Test with steps', async () => {
- await test.step('Step 1', async () => {
- expect(true).toBe(true);
- });
- await test.step('Step 2', async () => {
- expect(true).toBe(true);
- });
- expect(true).toBe(true);
- });
+// Pattern 3: Method-based ID assignment
+test('Test name', async ({ page }) => {
+ qase.id(1);
+ expect(true).toBe(true);
});
```
----
+### Add Metadata
-To run tests and create a test run, execute the command (for example from folder examples):
+Enhance your tests with additional information:
-```bash
-QASE_MODE=testops npx playwright test
-```
+```typescript
+import { qase } from 'playwright-qase-reporter';
-or
+test('Login test', async ({ page }) => {
+ qase.id(1);
+ qase.title('User can successfully login');
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'e2e',
+ });
+ qase.suite('Authentication / Login');
-```bash
-npm test
+ // Test logic
+ await page.goto('https://example.com/login');
+ expect(await page.title()).toBe('Login');
+});
```
-
-
-
+### Ignore Tests
-A test run will be performed and available at:
+Exclude specific tests from Qase reporting (test still runs, but results are not sent):
-```
-https://app.qase.io/run/QASE_PROJECT_CODE
+```typescript
+import { qase } from 'playwright-qase-reporter';
+
+test('This test runs but is not reported to Qase', async ({ page }) => {
+ qase.ignore();
+ expect(true).toBe(true);
+});
```
-
-
-
+### Test Result Statuses
-### Multi-Project Support
+| Playwright Result | Qase Status |
+|-------------------|-------------|
+| passed | passed |
+| failed | failed |
+| timedOut | failed |
+| skipped | skipped |
+| interrupted | skipped |
-Qase Playwright Reporter supports sending test results to multiple Qase projects simultaneously. You can specify different test case IDs for each project using `qase.projects(mapping)` or `qase.projectsTitle(name, mapping)`.
+> For more usage examples, see the [Usage Guide](docs/usage.md).
-For detailed information, configuration, and examples, see the [Multi-Project Support Guide](docs/MULTI_PROJECT.md).
+## Running Tests
-## Configuration
+```bash
+# Run all tests with Qase reporting
+npx playwright test
-Reporter options (\* - required):
-
-- `mode` - `testops`/`off` Enables reporter, default - `off`
-- `debug` - Enables debug logging, default - `false`
-- `environment` - To execute with the sending of the environment information
-- *`testops.api.token` - Token for API access, you can generate it [here](https://developers.qase.io/#authentication).
-- *`testops.project` - [Your project's code](https://help.qase.io/en/articles/9787250-how-do-i-find-my-project-code)
-- `testops.uploadAttachments` - Permission to send screenshots to Qase TMS
-- `testops.run.id` - Pass Run ID
-- `testops.run.title` - Set custom Run name, when new run is created
-- `testops.run.description` - Set custom Run description, when new run is created
-- `testops.run.complete` - Whether the run should be completed
-- `framework.browser.addAsParameter` - Whether to add the browser name as a parameter, default - `false`
-- `framework.browser.parameterName` - The name of the parameter to add the browser name to, default - `browser`
-- `framework.markAsFlaky` - Whether to mark tests as flaky if they passed after retries, default - `false`
-
-Example `playwright.config.js` config:
-
-```js
-const config = {
- use: {
- screenshot: 'only-on-failure',
- video: 'retain-on-failure',
- },
- reporter: [
- ['list'],
- [
- 'playwright-qase-reporter',
- {
- debug: true,
- testops: {
- api: {
- token: 'api_key',
- },
- project: 'project_code',
- uploadAttachments: true,
- run: {
- complete: true,
- },
- },
- framework: {
- browser: {
- addAsParameter: true,
- parameterName: 'Browser Name',
- },
- markAsFlaky: true,
- },
- },
- ],
- ],
-};
-module.exports = config;
-```
+# Run specific test file
+npx playwright test tests/auth.spec.ts
-You can check example configuration with multiple reporters in [example project](../examples/playwright/playwright.config.js).
+# Run tests with specific tag
+npx playwright test --grep "@smoke"
-Supported ENV variables:
+# Run tests in headed mode
+npx playwright test --headed
-- `QASE_MODE` - Same as `mode`
-- `QASE_DEBUG` - Same as `debug`
-- `QASE_ENVIRONMENT` - Same as `environment`
-- `QASE_TESTOPS_API_TOKEN` - Same as `testops.api.token`
-- `QASE_TESTOPS_PROJECT` - Same as `testops.project`
-- `QASE_TESTOPS_RUN_ID` - Pass Run ID from ENV and override reporter option `testops.run.id`
-- `QASE_TESTOPS_RUN_TITLE` - Same as `testops.run.title`
-- `QASE_TESTOPS_RUN_DESCRIPTION` - Same as `testops.run.description`
+# Run with specific browser
+npx playwright test --project=chromium
+
+# Run with custom test run title
+QASE_TESTOPS_RUN_TITLE="Nightly Regression" npx playwright test
+```
## Requirements
-We maintain the reporter on [LTS versions of Node.js](https://nodejs.org/en/about/releases/).
+- Node.js >= 14
+- Playwright >= 1.20.0
+
+## Documentation
+
+| Guide | Description |
+|-------|-------------|
+| [Usage Guide](docs/usage.md) | Complete usage reference with all methods and options |
+| [Attachments](docs/ATTACHMENTS.md) | Adding screenshots, logs, and files to test results |
+| [Steps](docs/STEPS.md) | Defining test steps for detailed reporting |
+| [Multi-Project Support](docs/MULTI_PROJECT.md) | Reporting to multiple Qase projects |
+| [Upgrade Guide](docs/UPGRADE.md) | Migration guide for breaking changes |
+
+## Examples
-`@playwright/test >= 1.16.3`
+See the [examples directory](../examples/) for complete working examples.
-
+## License
-[auth]: https://developers.qase.io/#authentication
+Apache License 2.0. See [LICENSE](LICENSE) for details.
diff --git a/qase-playwright/changelog.md b/qase-playwright/changelog.md
index a68d632e..87ccb60b 100644
--- a/qase-playwright/changelog.md
+++ b/qase-playwright/changelog.md
@@ -289,7 +289,7 @@ Right now, the reporter will convert all parameters to strings before sending th
## What's new
-- Using the `qase` annotation in a chain
+- Using the `Qase` annotation in a chain
```js
test('Ultimate Question of Life, The Universe, and Everything', async ({ page }) => {
@@ -446,7 +446,7 @@ npm install playwright-qase-reporter@latest
This release brings a major syntax change for specifying more test parameters.
-Old syntax allowed only test ID and title, and wasn't improving code readability:
+Old syntax allowed only test case ID and title, and wasn't improving code readability:
```js
test(qase(42, 'Ultimate Question of Life, The Universe, and Everything'), async ({ page }) => {
diff --git a/qase-playwright/docs/ATTACHMENTS.md b/qase-playwright/docs/ATTACHMENTS.md
new file mode 100644
index 00000000..bb4750db
--- /dev/null
+++ b/qase-playwright/docs/ATTACHMENTS.md
@@ -0,0 +1,321 @@
+# Attachments in Playwright
+
+This guide covers how to attach files, screenshots, logs, and other content to your Qase test results.
+
+---
+
+## Overview
+
+Qase Playwright Reporter supports attaching various types of content to test results:
+
+- **Files** — Attach files from the filesystem
+- **Screenshots** — Attach images captured during test execution
+- **Logs** — Attach text logs or console output
+- **Binary data** — Attach any binary content from memory
+
+Attachments can be added to:
+- **Test cases** — Visible in the overall test result
+- **Test steps** — Visible in specific step results
+
+---
+
+## Attaching Files
+
+### From File Path
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('Test with file attachment', async () => {
+ qase.attach({ paths: './test/attachments/test-file.txt' });
+
+ expect(true).toBe(true);
+});
+```
+
+### Multiple Files
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('Test with multiple attachments', async () => {
+ qase.attach({
+ paths: [
+ './test/attachments/file1.txt',
+ './test/attachments/file2.log',
+ './test/attachments/screenshot.png'
+ ]
+ });
+
+ expect(true).toBe(true);
+});
+```
+
+---
+
+## Attaching Content from Memory
+
+### Text Content
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('Test with text attachment', async () => {
+ qase.attach({
+ name: 'log.txt',
+ content: 'Test execution log content',
+ contentType: 'text/plain',
+ });
+
+ expect(true).toBe(true);
+});
+```
+
+### Binary Content (Screenshots)
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('Test with screenshot attachment', async ({ page }) => {
+ await page.goto('https://example.com');
+
+ const screenshot = await page.screenshot();
+
+ qase.attach({
+ name: 'screenshot.png',
+ content: screenshot,
+ contentType: 'image/png',
+ });
+
+ expect(true).toBe(true);
+});
+```
+
+### JSON Data
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('Test with JSON attachment', async () => {
+ const data = {
+ userId: 123,
+ status: 'active',
+ timestamp: new Date().toISOString(),
+ };
+
+ qase.attach({
+ name: 'test-data.json',
+ content: JSON.stringify(data, null, 2),
+ contentType: 'application/json',
+ });
+
+ expect(true).toBe(true);
+});
+```
+
+---
+
+## Attaching to Steps
+
+Attach content to a specific test step:
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('Test with step attachments', async ({ page }) => {
+ await qase.step('Navigate to page', async () => {
+ await page.goto('https://example.com');
+
+ qase.attach({
+ name: 'navigation-log.txt',
+ content: `Navigated to: ${page.url()}`,
+ contentType: 'text/plain',
+ });
+ });
+
+ await qase.step('Capture screenshot', async () => {
+ const screenshot = await page.screenshot();
+
+ qase.attach({
+ name: 'page-screenshot.png',
+ content: screenshot,
+ contentType: 'image/png',
+ });
+
+ expect(screenshot).toBeDefined();
+ });
+});
+```
+
+---
+
+## Method Reference
+
+### `qase.attach()`
+
+Attach content to the test case.
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `paths` | `string` or `string[]` | No* | Path(s) to file(s) to attach |
+| `content` | `string` or `Buffer` | No* | Content to attach |
+| `name` | `string` | No | Custom filename (auto-detected from path) |
+| `contentType` | `string` | No | MIME type (auto-detected from extension) |
+
+\* Either `paths` or `content` must be provided, but not both.
+
+**CommonJS:**
+```javascript
+const { qase } = require('playwright-qase-reporter');
+
+qase.attach({ paths: './path/to/file.txt' });
+```
+
+**ES Modules:**
+```javascript
+import { qase } from 'playwright-qase-reporter';
+
+qase.attach({ paths: './path/to/file.txt' });
+```
+
+---
+
+## MIME Types
+
+Common MIME types are auto-detected based on file extension:
+
+| Extension | MIME Type |
+|-----------|-----------|
+| `.png` | `image/png` |
+| `.jpg`, `.jpeg` | `image/jpeg` |
+| `.gif` | `image/gif` |
+| `.svg` | `image/svg+xml` |
+| `.txt` | `text/plain` |
+| `.log` | `text/plain` |
+| `.json` | `application/json` |
+| `.xml` | `application/xml` |
+| `.html` | `text/html` |
+| `.csv` | `text/csv` |
+| `.pdf` | `application/pdf` |
+| `.zip` | `application/zip` |
+
+For other file types, specify `contentType` explicitly.
+
+---
+
+## Common Use Cases
+
+### Playwright Screenshots
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('Test with automatic screenshot', async ({ page }) => {
+ await page.goto('https://example.com');
+ await page.click('button#submit');
+
+ // Capture and attach screenshot
+ const screenshot = await page.screenshot({ fullPage: true });
+
+ qase.attach({
+ name: 'full-page-screenshot.png',
+ content: screenshot,
+ contentType: 'image/png',
+ });
+
+ await expect(page.locator('.success')).toBeVisible();
+});
+```
+
+### API Response Logs
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('API request with response logging', async ({ request }) => {
+ const response = await request.get('https://api.example.com/users');
+ const data = await response.json();
+
+ qase.attach({
+ name: 'api-response.json',
+ content: JSON.stringify(data, null, 2),
+ contentType: 'application/json',
+ });
+
+ expect(response.status()).toBe(200);
+});
+```
+
+### Browser Console Logs
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('Test with browser console logs', async ({ page }) => {
+ const logs: string[] = [];
+
+ page.on('console', (msg) => {
+ logs.push(`${msg.type()}: ${msg.text()}`);
+ });
+
+ await page.goto('https://example.com');
+ await page.evaluate(() => {
+ console.log('Page loaded');
+ console.warn('Test warning');
+ console.error('Test error');
+ });
+
+ qase.attach({
+ name: 'console-logs.txt',
+ content: logs.join('\n'),
+ contentType: 'text/plain',
+ });
+
+ expect(logs.length).toBeGreaterThan(0);
+});
+```
+
+---
+
+## Troubleshooting
+
+### Attachments Not Appearing
+
+1. Verify the file path exists and is readable
+2. Check file permissions
+3. Enable debug logging to see upload status:
+ ```json
+ {
+ "debug": true
+ }
+ ```
+
+### Large Files
+
+Large attachments may slow down test execution. Consider:
+- Compressing logs before attaching
+- Using selective logging (e.g., only on failure)
+- Setting reasonable size limits
+
+### Binary Data Issues
+
+When attaching binary data, always specify:
+- `name` with appropriate extension
+- `contentType` if extension doesn't match content type
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Steps Guide](STEPS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-playwright/docs/MULTI_PROJECT.md b/qase-playwright/docs/MULTI_PROJECT.md
index b77e1cbb..8d714654 100644
--- a/qase-playwright/docs/MULTI_PROJECT.md
+++ b/qase-playwright/docs/MULTI_PROJECT.md
@@ -6,23 +6,54 @@ Qase Playwright Reporter supports sending test results to multiple Qase projects
* Different projects track the same functionality with different test case IDs
* You want to maintain separate test runs for different environments or teams
+---
+
## Configuration
For detailed configuration options, refer to the [qase-javascript-commons README](../../qase-javascript-commons/README.md#multi-project-support).
### Basic Multi-Project Configuration
-Set `mode` to `testops_multi` in your Playwright config (e.g. in `playwright.config.js` or `qase.config.json`) and add the `testops_multi` section with `default_project` and `projects`.
+Set `mode` to `testops_multi` in your Playwright config (e.g. in `playwright.config.ts` or `qase.config.json`) and add the `testops_multi` section with `default_project` and `projects`.
+
+**Example configuration:**
+
+```json
+{
+ "mode": "testops_multi",
+ "testops_multi": {
+ "default_project": "PROJ1",
+ "projects": [
+ {
+ "code": "PROJ1",
+ "api": {
+ "token": "your_api_token_for_proj1"
+ }
+ },
+ {
+ "code": "PROJ2",
+ "api": {
+ "token": "your_api_token_for_proj2"
+ }
+ }
+ ]
+ }
+}
+```
+
+---
## Specifying Project Mapping
+Playwright offers multiple approaches to map tests to multiple projects:
+
### Inside the test: `qase.projects(mapping)`
Call `qase.projects({ PROJECT_CODE: [id1, id2, ...], ... })` at the start of the test so the result is sent to the specified projects:
-```javascript
-const { test } = require('@playwright/test');
-const { qase } = require('playwright-qase-reporter');
+```typescript
+import { test } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
test('login and checkout', async ({ page }) => {
qase.projects({ PROJ1: [1, 2], PROJ2: [3] });
@@ -35,7 +66,7 @@ test('login and checkout', async ({ page }) => {
Use `qase.projectsTitle('Test name', { PROJ1: [1], PROJ2: [2] })` as the test name; the reporter parses the generated title and sets the mapping:
-```javascript
+```typescript
test(qase.projectsTitle('Login flow', { PROJ1: [100], PROJ2: [200] }), async ({ page }) => {
await page.goto('/login');
});
@@ -45,21 +76,121 @@ test(qase.projectsTitle('Login flow', { PROJ1: [100], PROJ2: [200] }), async ({
You can attach a `QaseProjects` annotation with JSON mapping (e.g. in `test.info().annotations`). The reporter reads it and applies the project mapping.
+**Key points:**
+
+- Single project: `qase.id(100)` (reports to default_project)
+- Multi-project (in test): `qase.projects({ PROJ1: [100], PROJ2: [200] })`
+- Multi-project (in title): `qase.projectsTitle('Test name', { PROJ1: [100], PROJ2: [200] })`
+- Multiple IDs per project: `qase.projects({ PROJ1: [10, 11], PROJ2: [20] })`
+
+Project codes (e.g. `PROJ1`, `PROJ2`) must match `testops_multi.projects[].code` in your config.
+
+---
+
## Tests Without Project Mapping
Tests that do not call `qase.projects()` or use `qase.projectsTitle()` and have no QaseProjects annotation are sent to the `default_project` from your configuration. If they have a single-project Qase ID, that ID is used for the default project.
+---
+
## Important Notes
-1. **Project codes must match**: Codes in `qase.projects({ PROJ1: [1], ... })` must match `testops_multi.projects[].code`.
+1. **Project codes must match**: Codes in `qase.projects({ PROJ1: [1], ... })` must match `testops_multi.projects[].code` in config.
2. **Mode**: Use `mode: 'testops_multi'` in reporter config.
3. **Each project gets its own run**: Each project in `testops_multi.projects` will have a separate test run created.
+4. **API tokens**: Each project in `testops_multi.projects[]` can have its own API token for separate authentication.
+
+---
## Examples
See the [multi-project Playwright example](../../examples/multiProject/playwright/) for a complete runnable setup.
+### Complete Example
+
+Here's a complete Playwright test file showing multi-project usage:
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test.describe('Multi-project test suite', () => {
+ // Test reported to two projects using qase.projects() in test
+ test('User can login successfully', async ({ page }) => {
+ qase.projects({ PROJ1: [1], PROJ2: [2] });
+
+ await page.goto('https://example.com/login');
+ await page.fill('#username', 'testuser');
+ await page.fill('#password', 'password123');
+ await page.click('#login-button');
+
+ await expect(page).toHaveURL(/dashboard/);
+ });
+
+ // Test reported to two projects using projectsTitle
+ test(qase.projectsTitle('Checkout process works', { PROJ1: [10, 11], PROJ2: [20] }), async ({ page }) => {
+ await page.goto('https://example.com/cart');
+ await page.click('#checkout-button');
+
+ await expect(page.locator('.success-message')).toBeVisible();
+ });
+
+ // Combining multi-project with other Qase methods
+ test('User registration flow', async ({ page }) => {
+ qase.projects({ PROJ1: [100], PROJ2: [200] });
+ qase.title('Complete user registration with verification');
+ qase.fields({ severity: 'critical', priority: 'high' });
+
+ await page.goto('https://example.com/register');
+ await page.fill('#email', 'newuser@example.com');
+ await page.click('#register-button');
+
+ await expect(page.locator('.confirmation')).toBeVisible();
+ });
+
+ // Single-project test (uses default_project)
+ test('Test reported to default project', async ({ page }) => {
+ qase.id(50);
+
+ await page.goto('https://example.com');
+ await expect(page).toHaveTitle(/Example/);
+ });
+
+ // Test without Qase metadata (goes to default_project without case ID)
+ test('Regular test without Qase tracking', async ({ page }) => {
+ await page.goto('https://example.com');
+ await expect(page).toHaveTitle(/Example/);
+ });
+});
+```
+
+---
+
## Troubleshooting
-* Ensure `mode` is `testops_multi` and project codes in your code match the config.
-* If results do not appear, check that `qase.projects()` is called (or the title/annotation is set) before the test body runs.
+### Results Not Appearing in All Projects
+
+* Ensure `mode` is `testops_multi` (not `TestOps`) and project codes in your code match the config
+* Check that project codes match config codes exactly (case-sensitive)
+* Ensure each project has a valid API token with write permissions
+* If results do not appear, check that `qase.projects()` is called (or the title/annotation is set) before the test body runs
+
+### Wrong Test Cases Linked
+
+* Verify the mapping object has correct project codes as keys
+* Check that test case IDs exist in the respective projects
+* Enable debug logging to see how the reporter parses multi-project markers
+
+### Default Project Not Working
+
+* Ensure `default_project` is set in `testops_multi` config
+* Verify the default project code matches one of the projects in the `projects` array
+* Tests without `qase.projects()` or `qase.projectsTitle()` will only report to the default project
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Examples](../../examples/multiProject/playwright/)
diff --git a/qase-playwright/docs/STEPS.md b/qase-playwright/docs/STEPS.md
new file mode 100644
index 00000000..fc192955
--- /dev/null
+++ b/qase-playwright/docs/STEPS.md
@@ -0,0 +1,429 @@
+# Test Steps in Playwright
+
+This guide covers how to define and report test steps for detailed execution tracking in Qase.
+
+---
+
+## Overview
+
+Test steps provide granular visibility into test execution. Each step is reported separately, showing:
+
+- Step name and description
+- Step status (passed/failed)
+- Step duration
+- Attachments (if any)
+- Error details (on failure)
+
+---
+
+## Defining Steps
+
+### Using Async Function
+
+Playwright supports both `qase.step()` and native `test.step()` for defining test steps:
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('Test with multiple steps using qase.step()', async ({ page }) => {
+ await qase.step('Initialize the environment', async () => {
+ await page.goto('https://example.com');
+ });
+
+ await qase.step('Test Core Functionality of the app', async () => {
+ await page.click('#action-button');
+ });
+
+ await qase.step('Verify Expected Behavior of the app', async () => {
+ await expect(page.locator('.result')).toBeVisible();
+ });
+});
+```
+
+### Using Native test.step()
+
+Playwright's built-in `test.step()` is also fully supported and reported to Qase:
+
+```typescript
+import { test, expect } from '@playwright/test';
+
+test('Test with native test.step()', async ({ page }) => {
+ await test.step('Initialize the environment', async () => {
+ await page.goto('https://example.com');
+ });
+
+ await test.step('Test Core Functionality of the app', async () => {
+ await page.click('#action-button');
+ });
+
+ await test.step('Verify Expected Behavior of the app', async () => {
+ await expect(page.locator('.result')).toBeVisible();
+ });
+});
+```
+
+**When to use which:**
+- Use `test.step()` for Playwright-native integration and when you want steps to appear in Playwright's trace viewer
+- Use `qase.step()` for cross-framework consistency if you're maintaining tests across multiple frameworks
+- Both methods are reported to Qase with the same level of detail
+
+### Step Parameters
+
+Steps can include parameters for dynamic naming:
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('Test with dynamic step names', async ({ page }) => {
+ const username = 'john@example.com';
+
+ await qase.step(`Login as user ${username}`, async () => {
+ await page.fill('#email', username);
+ await page.fill('#password', 'password');
+ await page.click('button[type="submit"]');
+ });
+
+ await qase.step(`Verify ${username} profile loaded`, async () => {
+ await expect(page.locator('.user-email')).toHaveText(username);
+ });
+});
+```
+
+---
+
+## Nested Steps
+
+Create hierarchical step structures with either method:
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('Test with nested steps using qase.step()', async ({ page }) => {
+ await qase.step('Complete user registration', async () => {
+ await qase.step('Fill registration form', async () => {
+ await page.fill('#name', 'John Doe');
+ await page.fill('#email', 'john@example.com');
+ });
+
+ await qase.step('Submit registration', async () => {
+ await page.click('button[type="submit"]');
+ });
+ });
+
+ await qase.step('Verify registration success', async () => {
+ await expect(page.locator('.success-message')).toBeVisible();
+ });
+});
+
+test('Test with nested steps using test.step()', async ({ page }) => {
+ await test.step('Complete user registration', async () => {
+ await test.step('Fill registration form', async () => {
+ await page.fill('#name', 'John Doe');
+ await page.fill('#email', 'john@example.com');
+ });
+
+ await test.step('Submit registration', async () => {
+ await page.click('button[type="submit"]');
+ });
+ });
+
+ await test.step('Verify registration success', async () => {
+ await expect(page.locator('.success-message')).toBeVisible();
+ });
+});
+```
+
+---
+
+## Steps with Expected Result and Data
+
+Define expected results and data for steps:
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('Test with expected results', async ({ page }) => {
+ await qase.step(
+ 'Click button',
+ async () => {
+ await page.click('#submit-button');
+ },
+ 'Button should be clicked',
+ 'Button data'
+ );
+
+ await qase.step(
+ 'Fill form',
+ async () => {
+ await page.fill('#input-field', 'test value');
+ },
+ 'Form should be filled',
+ 'Form input data'
+ );
+
+ await qase.step(
+ 'Submit form',
+ async () => {
+ await page.click('button[type="submit"]');
+ },
+ 'Form should be submitted',
+ 'Form submission data'
+ );
+});
+```
+
+**Signature:**
+```typescript
+await qase.step(
+ name: string,
+ callback: () => Promise | void,
+ expectedResult?: string,
+ data?: string
+): Promise
+```
+
+---
+
+## Steps with Attachments
+
+Attach content to a specific step:
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('Test with step attachments', async ({ page }) => {
+ await qase.step('Capture application state', async () => {
+ const screenshot = await page.screenshot();
+
+ qase.attach({
+ name: 'app-state.png',
+ content: screenshot,
+ contentType: 'image/png',
+ });
+ });
+
+ await qase.step('Verify state', async () => {
+ await expect(page.locator('.status')).toHaveText('active');
+ });
+});
+```
+
+---
+
+## Step Status
+
+Steps automatically inherit status from execution:
+
+| Execution | Step Status |
+|-----------|-------------|
+| Completes normally | Passed |
+| Throws Error/AssertionError | Failed |
+| Other exception | Invalid |
+
+---
+
+## Best Practices
+
+### Keep Steps Atomic
+
+Each step should represent a single action:
+
+```typescript
+// Good: One action per step
+await test.step('Click login button', async () => {
+ await page.click('#login-btn');
+});
+
+await test.step('Enter username', async () => {
+ await page.fill('#username', 'user');
+});
+
+// Avoid: Multiple actions in one step
+await test.step('Fill form and submit', async () => { // Too broad
+ await page.fill('#username', 'user');
+ await page.fill('#password', 'pass');
+ await page.click('#submit');
+});
+```
+
+### Use Descriptive Names
+
+```typescript
+// Good: Clear action description
+await qase.step('Verify user is redirected to dashboard', async () => {
+ await expect(page).toHaveURL(/.*dashboard/);
+});
+
+// Avoid: Vague names
+await qase.step('Check page', async () => {
+ await expect(page).toHaveURL(/.*dashboard/);
+});
+```
+
+### Include Context in Step Names
+
+```typescript
+// Good: Include relevant context
+await qase.step(`Add product '${productName}' to cart`, async () => {
+ await page.click(`[data-product="${productName}"] .add-to-cart`);
+});
+
+// Better than generic:
+await qase.step('Add product', async () => {
+ await page.click(`[data-product="${productName}"] .add-to-cart`);
+});
+```
+
+---
+
+## Common Patterns
+
+### Page Object Steps
+
+```typescript
+import { test, expect, Page } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+class LoginPage {
+ constructor(private page: Page) {}
+
+ async login(username: string, password: string) {
+ await qase.step(`Enter username: ${username}`, async () => {
+ await this.page.fill('#email', username);
+ });
+
+ await qase.step('Enter password', async () => {
+ await this.page.fill('#password', password);
+ });
+
+ await qase.step('Click login button', async () => {
+ await this.page.click('button[type="submit"]');
+ });
+ }
+}
+
+test('User can login', async ({ page }) => {
+ const loginPage = new LoginPage(page);
+ await page.goto('https://example.com/login');
+ await loginPage.login('user@example.com', 'password');
+});
+```
+
+### API Testing Steps
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('API returns correct user data', async ({ request }) => {
+ let response;
+
+ await qase.step('Send GET request to /api/users/1', async () => {
+ response = await request.get('https://api.example.com/users/1');
+ });
+
+ await qase.step('Verify response status is 200', async () => {
+ expect(response.status()).toBe(200);
+ });
+
+ await qase.step('Verify response contains user data', async () => {
+ const data = await response.json();
+ expect(data.id).toBe(1);
+ expect(data.name).toBeDefined();
+ });
+});
+```
+
+### Setup/Teardown Steps
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test.describe('User tests', () => {
+ test.beforeEach(async ({ page }) => {
+ await qase.step('Setup: Navigate to application', async () => {
+ await page.goto('https://example.com');
+ });
+
+ await qase.step('Setup: Authenticate user', async () => {
+ await page.fill('#email', 'test@example.com');
+ await page.fill('#password', 'password');
+ await page.click('button[type="submit"]');
+ });
+ });
+
+ test.afterEach(async ({ page }) => {
+ await qase.step('Cleanup: Logout user', async () => {
+ await page.click('#logout-button');
+ });
+ });
+
+ test('Test user operations', async ({ page }) => {
+ await qase.step('Perform user action', async () => {
+ await page.click('#user-action');
+ });
+ });
+});
+```
+
+---
+
+## Troubleshooting
+
+### Steps Not Appearing
+
+1. Verify the step function is properly imported from `playwright-qase-reporter`
+2. Check that steps are executed within a test context
+3. Enable debug logging to trace step recording
+4. **Ensure you're using `await` with async step callbacks** - Missing `await` is the most common issue
+
+```typescript
+// Incorrect: Missing await
+qase.step('Step name', async () => { // Step won't be recorded properly
+ // Logic
+});
+
+// Correct: Using await
+await qase.step('Step name', async () => {
+ // Logic
+});
+```
+
+### Nested Steps Flattened
+
+Ensure you're using the async callbacks correctly for nesting:
+
+```typescript
+// Correct: Nested callbacks
+await qase.step('Parent step', async () => {
+ await qase.step('Child step', async () => {
+ // Child step logic
+ });
+});
+
+// Incorrect: Sequential, not nested
+await qase.step('Step 1', async () => {
+ // Step 1 logic
+});
+await qase.step('Step 2', async () => { // Not nested under Step 1
+ // Step 2 logic
+});
+```
+
+### Step Duration Shows 0
+
+Steps need measurable execution time. Very fast steps may show 0ms duration.
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Attachments Guide](ATTACHMENTS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-playwright/docs/UPGRADE.md b/qase-playwright/docs/UPGRADE.md
new file mode 100644
index 00000000..0075e856
--- /dev/null
+++ b/qase-playwright/docs/UPGRADE.md
@@ -0,0 +1,310 @@
+# Upgrade Guide: Playwright Reporter
+
+This guide covers migration steps between major versions of the Qase Playwright Reporter.
+
+---
+
+## Version History
+
+| Version | Release Date | Node.js Support | Key Changes |
+|---------|--------------|-----------------|-------------|
+| 2.2.0 | January 2026 | >= 14 | Current stable release with improved reporter functionality |
+| 2.1.0 | December 2025 | >= 14 | Enhanced metadata handling and multi-project support |
+| 2.0.0 | August 2025 | >= 14 | Complete rewrite with new architecture and API |
+
+---
+
+## Upgrading to 2.x
+
+### Breaking Changes
+
+The playwright-qase-reporter started with the v2.x architecture, leveraging the unified qase-javascript-commons library for consistent reporting across all test frameworks. If you are using v2.x, you are already on the latest architecture.
+
+**No migration from a previous major version is required for playwright-qase-reporter.**
+
+### Current Version Features
+
+Version 2.2.0 includes:
+
+- Full support for Qase TestOps API with batch result upload
+- Dual test case linking patterns: wrapper function and method-based annotation
+- Rich metadata support: titles, fields, suites, parameters, comments
+- Native Playwright test steps and qase.step() for custom steps
+- Screenshot and file attachments with `qase.attach()`
+- Multi-project support for reporting to multiple Qase projects
+- Flexible configuration via `qase.config.json` or playwright.config.ts
+
+---
+
+## Configuration
+
+### Current Format (v2.x)
+
+Configuration uses the modern qase-javascript-commons format:
+
+```typescript
+// playwright.config.ts
+import { defineConfig } from '@playwright/test';
+
+export default defineConfig({
+ reporter: [
+ ['list'],
+ [
+ 'playwright-qase-reporter',
+ {
+ mode: 'testops',
+ debug: false,
+ testops: {
+ api: {
+ token: process.env.QASE_API_TOKEN,
+ },
+ project: 'DEMO',
+ run: {
+ title: 'Playwright Automated Run',
+ description: 'Test run from CI/CD pipeline',
+ complete: true,
+ },
+ batch: {
+ size: 100,
+ },
+ },
+ },
+ ],
+ ],
+});
+```
+
+**Alternative: qase.config.json**
+
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "api": {
+ "token": "api_token_here"
+ },
+ "project": "DEMO",
+ "run": {
+ "complete": true
+ }
+ }
+}
+```
+
+---
+
+## Import Pattern
+
+### Current Import (v2.x)
+
+```typescript
+import { qase } from 'playwright-qase-reporter';
+```
+
+**Note:** Playwright uses the root package export (no subpath required).
+
+---
+
+## API Reference
+
+### Test Case ID Linking
+
+**Wrapper Function Pattern:**
+
+```typescript
+import { qase } from 'playwright-qase-reporter';
+import { test, expect } from '@playwright/test';
+
+test(qase(1, 'User can login'), async ({ page }) => {
+ await page.goto('https://example.com');
+ expect(await page.title()).toBe('Example');
+});
+
+test(qase([1, 2], 'Multiple IDs'), async ({ page }) => {
+ // Test code
+});
+```
+
+**Method-Based Pattern:**
+
+```typescript
+test('Simple test', async ({ page }) => {
+ qase.title('Custom test title');
+ qase.fields({ severity: 'high', priority: 'medium' });
+
+ await page.goto('https://example.com');
+ expect(await page.title()).toBe('Example');
+});
+```
+
+### Metadata Methods
+
+```typescript
+test('Test with metadata', async ({ page }) => {
+ qase.title('Custom test title');
+ qase.fields({ severity: 'critical', priority: 'high', layer: 'e2e' });
+ qase.suite('Authentication / Login');
+ qase.comment('This test covers the main login flow');
+ qase.parameters({ browser: 'chromium', viewport: '1920x1080' });
+
+ // Test code
+});
+```
+
+### Steps
+
+**Custom Steps with qase.step():**
+
+```typescript
+test('Test with custom steps', async ({ page }) => {
+ await qase.step('Navigate to login page', async () => {
+ await page.goto('https://example.com/login');
+ });
+
+ await qase.step('Enter credentials', async () => {
+ await page.fill('#username', 'user@example.com');
+ await page.fill('#password', 'password123');
+ });
+
+ await qase.step('Verify login success', async () => {
+ await expect(page.locator('.dashboard')).toBeVisible();
+ });
+});
+```
+
+**Native Playwright Steps:**
+
+```typescript
+test('Test with native steps', async ({ page }) => {
+ await test.step('Navigate to page', async () => {
+ await page.goto('https://example.com');
+ });
+
+ await test.step('Verify page loaded', async () => {
+ await expect(page).toHaveTitle('Example');
+ });
+});
+```
+
+Both patterns are reported to Qase. You can mix them in the same test if needed.
+
+### Attachments
+
+**Screenshot Attachment:**
+
+```typescript
+const screenshot = await page.screenshot();
+qase.attach({
+ name: 'screenshot.png',
+ content: screenshot,
+ contentType: 'image/png',
+});
+```
+
+**File Attachment:**
+
+```typescript
+qase.attach({ paths: '/path/to/log.txt' });
+```
+
+**Content Attachment:**
+
+```typescript
+qase.attach({
+ name: 'test-data.json',
+ content: JSON.stringify({ key: 'value' }),
+ contentType: 'application/json',
+});
+```
+
+---
+
+## Compatibility Notes
+
+### Node.js Version Support
+
+- **Current (2.2.0):** Node.js >= 14
+
+### Playwright Version Support
+
+- **Current (2.2.0):** Playwright >= 1.16.3
+- Tested with Playwright 1.40+
+
+### Framework Compatibility
+
+- TypeScript recommended (full type definitions included)
+- ES Modules required for Playwright projects
+- Works with all Playwright test runner features (fixtures, annotations, etc.)
+
+---
+
+## Troubleshooting
+
+### Common Issues
+
+#### Issue: Module not found after installation
+
+**Solution:** Ensure you're importing from the correct package:
+
+```typescript
+// Correct
+import { qase } from 'playwright-qase-reporter';
+
+// Incorrect - no subpath needed
+import { qase } from 'playwright-qase-reporter/playwright';
+```
+
+#### Issue: Reporter not configured
+
+**Solution:** Verify the reporter is added to your playwright.config.ts:
+
+```typescript
+reporter: [
+ ['playwright-qase-reporter', { /* options */ }],
+],
+```
+
+#### Issue: Tests not reported to Qase
+
+**Solution:** Check that:
+1. API token is set: `process.env.QASE_API_TOKEN`
+2. Project code is correct: `project: 'YOUR_PROJECT_CODE'`
+3. Mode is set to TestOps: `mode: 'TestOps'`
+
+#### Issue: Configuration format errors
+
+**Solution:** Ensure the configuration follows the correct structure:
+
+```typescript
+{
+ mode: 'testops',
+ testops: {
+ api: { token: 'your_token' },
+ project: 'YOUR_PROJECT_CODE',
+ }
+}
+```
+
+---
+
+## Getting Help
+
+If you encounter issues:
+
+1. Check the [GitHub Issues](https://github.com/qase-tms/qase-javascript/issues)
+2. Review the CHANGELOG
+3. Open a new issue with:
+ - Current version (2.2.0)
+ - Playwright version
+ - Error messages
+ - Configuration file (without sensitive data)
+ - Steps to reproduce
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Multi-Project Support](MULTI_PROJECT.md)
+- CHANGELOG
diff --git a/qase-playwright/docs/usage.md b/qase-playwright/docs/usage.md
index 788f8225..c5eac6e5 100644
--- a/qase-playwright/docs/usage.md
+++ b/qase-playwright/docs/usage.md
@@ -1,298 +1,997 @@
# Qase Integration in Playwright
-This guide demonstrates how to integrate Qase with Playwright, providing instructions on how to add Qase IDs, titles,
-fields, suites, comments, and file attachments to your test cases.
+This guide provides comprehensive instructions for integrating Qase with Playwright.
+
+> **Configuration:** For complete configuration reference including all available options, environment variables, and examples, see the [qase-javascript-commons README](../../qase-javascript-commons/README.md).
---
-## Adding QaseID to a Test
+## Table of Contents
+
+- [Adding QaseID](#adding-qaseid)
+- [Adding Title](#adding-title)
+- [Adding Fields](#adding-fields)
+- [Adding Suite](#adding-suite)
+- [Ignoring Tests](#ignoring-tests)
+- [Muting Tests](#muting-tests)
+- [Working with Attachments](#working-with-attachments)
+- [Working with Steps](#working-with-steps)
+- [Working with Parameters](#working-with-parameters)
+- [Multi-Project Support](#multi-project-support)
+- [Running Tests](#running-tests)
+- [Integration Patterns](#integration-patterns)
+- [Common Use Cases](#common-use-cases)
+- [Troubleshooting](#troubleshooting)
+- [Complete Examples](#complete-examples)
+
+- [See Also](#see-also)
+---
-To associate a QaseID with a test in Playwright, use the `qase` function. This function accepts a single integer
-representing the test's ID in Qase.
+## Adding QaseID
-### Example
+Playwright offers **three flexible patterns** for linking automated tests to existing test cases in Qase.
-```javascript
+### Pattern 1: Wrapper Function (Single ID)
+
+```typescript
+import { test, expect } from '@playwright/test';
import { qase } from 'playwright-qase-reporter';
-test(qase(1, 'test'), async ({ page }) => {
- await page.goto('https://example.com');
+test(qase(1, 'User can login'), async ({ page }) => {
+ await page.goto('https://example.com/login');
+ expect(await page.title()).toBe('Login');
});
+```
+
+### Pattern 2: Wrapper Function (Multiple IDs)
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
-test(qase([1, 2, 3], 'test'), async ({ page }) => {
+test(qase([1, 2, 3], 'Test covering multiple scenarios'), async ({ page }) => {
+ // This test result will be reported to test cases 1, 2, and 3
await page.goto('https://example.com');
+ expect(await page.title()).toBe('Example Domain');
});
```
----
+### Pattern 3: Method-based ID Assignment
-## Adding a Title to a Test
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
-You can provide a title for your test using the `qase.title` function. The function accepts a string, which will be
-used as the test's title in Qase. If no title is provided, the test method name will be used by default.
+test('User can login', async ({ page }) => {
+ qase.id(1);
+ await page.goto('https://example.com/login');
+ expect(await page.title()).toBe('Login');
+});
+```
-### Example
+### Multi-Project Support
+
+To send test results to multiple Qase projects simultaneously, see the [Multi-Project Support Guide](MULTI_PROJECT.md).
+
+---
+
+## Adding Title
-```javascript
+Set a custom title for the test case (overrides auto-generated title):
+
+```typescript
+import { test, expect } from '@playwright/test';
import { qase } from 'playwright-qase-reporter';
-test('test', async ({ page }) => {
- qase.title('Title');
- await page.goto('https://example.com');
+test('Login test', async ({ page }) => {
+ qase.title('User can successfully login with valid credentials');
+ await page.goto('https://example.com/login');
+ expect(await page.title()).toBe('Login');
});
```
---
-## Adding Fields to a Test
+## Adding Fields
-The `qase.fields` function allows you to add additional metadata to a test case. You can specify multiple fields to
-enhance test case information in Qase.
+Add metadata to your test cases using fields. Both system and custom fields are supported.
### System Fields
-- `description` — Description of the test case.
-- `preconditions` — Preconditions for the test case.
-- `postconditions` — Postconditions for the test case.
-- `severity` — Severity of the test case (e.g., `critical`, `major`).
-- `priority` — Priority of the test case (e.g., `high`, `low`).
-- `layer` — Test layer (e.g., `UI`, `API`).
+| Field | Description | Example Values |
+|-------|-------------|----------------|
+| `description` | Test case description | Any text |
+| `preconditions` | Test preconditions | Any text (supports Markdown) |
+| `postconditions` | Test postconditions | Any text |
+| `severity` | Test severity | `blocker`, `critical`, `major`, `normal`, `minor`, `trivial` |
+| `priority` | Test priority | `high`, `medium`, `low` |
+| `layer` | Test layer | `e2e`, `api`, `unit` |
### Example
-```javascript
+```typescript
+import { test, expect } from '@playwright/test';
import { qase } from 'playwright-qase-reporter';
-test('test', async ({ page }) => {
- qase.fields({ description: "Description", preconditions: "Preconditions" });
- await page.goto('https://example.com');
+test(qase(1, 'Login test'), async ({ page }) => {
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'e2e',
+ description: 'Verify user authentication flow',
+ preconditions: 'User must be registered in the system',
+ postconditions: 'User session is created',
+ });
+
+ // Test logic
+ await page.goto('https://example.com/login');
+ expect(await page.title()).toBe('Login');
});
```
---
-## Adding a Suite to a Test
+## Adding Suite
-To assign a suite or sub-suite to a test, use the `qase.suite` function. It can receive a suite name, and optionally a
-sub-suite, both as strings.
+Organize tests into suites and sub-suites:
-### Example
+### Simple Suite
-```javascript
+```typescript
+import { test } from '@playwright/test';
import { qase } from 'playwright-qase-reporter';
-test('test', async ({ page }) => {
- qase.suite("Suite 01");
- await page.goto('https://example.com');
+test('Login test', async ({ page }) => {
+ qase.suite('Authentication');
+ await page.goto('https://example.com/login');
});
+```
-test('test', async ({ page }) => {
- qase.suite("Suite 01\tSuite 02");
- await page.goto('https://example.com');
+### Nested Suites
+
+```typescript
+import { test } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('Login test', async ({ page }) => {
+ qase.suite('Authentication\tLogin\tPositive Cases');
+ await page.goto('https://example.com/login');
});
```
---
-## Ignoring a Test in Qase
+## Ignoring Tests
-To exclude a test from being reported to Qase (while still executing the test in Playwright), use the `qase.ignore`
-function. The test will run, but its result will not be sent to Qase.
-
-### Example
+Exclude a test from Qase reporting. The test still executes, but results are not sent to Qase:
-```javascript
+```typescript
+import { test, expect } from '@playwright/test';
import { qase } from 'playwright-qase-reporter';
-test('test', async ({ page }) => {
+test('This test runs but is not reported', async ({ page }) => {
qase.ignore();
- await page.goto('https://example.com');
+ expect(true).toBe(true);
});
```
---
-## Adding a Comment to a Test
-
-You can attach comments to the test results in Qase using the `qase.comment` function. The comment will be displayed
-alongside the test execution details in Qase.
+## Muting Tests
-### Example
+Mark a test as muted. Muted tests are reported but do not affect the test run status:
-```javascript
+```typescript
+import { test, expect } from '@playwright/test';
import { qase } from 'playwright-qase-reporter';
-test('test', async ({ page }) => {
- qase.comment("Some comment");
- await page.goto('https://example.com');
+test(qase(1, 'Known failing test'), async ({ page }) => {
+ qase.mute();
+ expect(false).toBe(true); // This failure won't affect the run status
});
```
---
-## Attaching Files to a Test
+## Working with Attachments
-To attach files to a test result, use the `qase.attach` function. This method supports attaching one or multiple files,
-along with optional file names, comments, and file types.
+Attach files, screenshots, logs, and other content to your test results.
-### Example
+### Attach File from Path
-```javascript
+```typescript
+import { test } from '@playwright/test';
import { qase } from 'playwright-qase-reporter';
-test('test', async ({ page }) => {
- qase.attach({ name: 'attachment.txt', content: 'Hello, world!', contentType: 'text/plain' });
- qase.attach({ paths: '/path/to/file' });
- qase.attach({ paths: ['/path/to/file', '/path/to/another/file'] });
+test('Test with file attachment', async ({ page }) => {
+ // Single file
+ qase.attach({ paths: '/path/to/screenshot.png' });
+
+ // Multiple files
+ qase.attach({
+ paths: ['/path/to/log.txt', '/path/to/screenshot.png']
+ });
+
await page.goto('https://example.com');
});
```
-## Adding Parameters to a Test
+### Attach Content from Code
-You can add parameters to a test case using the `qase.parameters` function. This function accepts an object with
-parameter names and values.
+```typescript
+import { test } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
-### Example
+test('Test with content attachment', async ({ page }) => {
+ qase.attach({
+ name: 'execution-log.txt',
+ content: 'Test execution details...',
+ contentType: 'text/plain',
+ });
+
+ await page.goto('https://example.com');
+});
+```
+
+### Attach Playwright Screenshot
-```javascript
+```typescript
+import { test } from '@playwright/test';
import { qase } from 'playwright-qase-reporter';
-test('test', async ({ page }) => {
- qase.parameters({ param1: 'value1', param2: 'value2' });
+test('Test with screenshot', async ({ page }) => {
await page.goto('https://example.com');
+
+ const screenshot = await page.screenshot();
+ qase.attach({
+ name: 'page-screenshot.png',
+ content: screenshot,
+ contentType: 'image/png',
+ });
});
```
-## Adding Group Parameters to a Test
+### Supported MIME Types
-To add group parameters to a test case, use the `qase.groupParameters` function. This function accepts an object with
-group parameter names and values.
+Common MIME types are auto-detected. You can also specify explicitly:
-### Example
+| Extension | MIME Type |
+|-----------|-----------|
+| `.png` | `image/png` |
+| `.jpg`, `.jpeg` | `image/jpeg` |
+| `.gif` | `image/gif` |
+| `.txt` | `text/plain` |
+| `.json` | `application/json` |
+| `.xml` | `application/xml` |
+| `.html` | `text/html` |
+| `.pdf` | `application/pdf` |
+
+> For more details, see [Attachments Guide](ATTACHMENTS.md).
+
+---
-```javascript
+## Working with Steps
+
+Define test steps for detailed reporting in Qase. Playwright supports **both** native `test.step()` and `qase.step()`.
+
+### Using qase.step (Async)
+
+```typescript
+import { test, expect } from '@playwright/test';
import { qase } from 'playwright-qase-reporter';
-test('test', async ({ page }) => {
- qase.parameters({ param1: 'value1', param2: 'value2' });
- qase.groupParameters({ param3: 'value3', param4: 'value4' });
- await page.goto('https://example.com');
+test('Test with qase.step', async ({ page }) => {
+ await qase.step('Navigate to login page', async () => {
+ await page.goto('https://example.com/login');
+ });
+
+ await qase.step('Fill login form', async () => {
+ await page.fill('#email', 'user@example.com');
+ await page.fill('#password', 'password123');
+ });
+
+ await qase.step('Submit form', async () => {
+ await page.click('button[type="submit"]');
+ });
+
+ await qase.step('Verify successful login', async () => {
+ await expect(page.locator('.dashboard')).toBeVisible();
+ });
});
```
-## Adding Steps to a Test
+### Using Native test.step
-You can add steps to a test case using the `qase.step` function. This function accepts a string for the action, and optionally an expected result and input data, which will be used as the step description in Qase.
+```typescript
+import { test, expect } from '@playwright/test';
-### Example
+test('Test with native test.step', async ({ page }) => {
+ await test.step('Navigate to page', async () => {
+ await page.goto('https://example.com');
+ });
+
+ await test.step('Verify title', async () => {
+ await expect(page).toHaveTitle('Example Domain');
+ });
+});
+```
-```javascript
+### Nested Steps
+
+```typescript
+import { test } from '@playwright/test';
import { qase } from 'playwright-qase-reporter';
-test('test', async ({ page }) => {
- await test.step(qase.step('Some step'), async () => {
- // some actions
+test('Test with nested steps', async ({ page }) => {
+ await qase.step('Authentication flow', async () => {
+ await qase.step('Open login page', async () => {
+ await page.goto('https://example.com/login');
+ });
+
+ await qase.step('Enter credentials', async () => {
+ await page.fill('#email', 'user@example.com');
+ await page.fill('#password', 'password123');
+ });
+
+ await qase.step('Click login button', async () => {
+ await page.click('button[type="submit"]');
+ });
});
- await page.goto('https://example.com');
});
```
-### Example with Expected Result and Data
+### Steps with Expected Result
-```javascript
+```typescript
+import { test } from '@playwright/test';
import { qase } from 'playwright-qase-reporter';
-test('test', async ({ page }) => {
- await test.step(qase.step('Click button', 'Button should be clicked', 'Button data'), async () => {
- await page.click('button');
- });
- await page.goto('https://example.com');
+test('Test with expected results', async ({ page }) => {
+ await qase.step(
+ 'Click login button',
+ async () => {
+ await page.click('button[type="submit"]');
+ },
+ 'Button should be clicked',
+ 'Login button data'
+ );
});
```
-## Annotations
+> For more details, see [Steps Guide](STEPS.md).
-Playwright Qase reporter supports test annotations for setting Qase IDs, titles, and suites.
+---
-### Example
+## Working with Parameters
-```javascript
-test('test',
- {
- annotation: { type: 'QaseID', description: '1' },
- },
- async ({ page }) => {
+Report parameterized test data to Qase.
+
+### Basic Parameterized Test
+
+```typescript
+import { test } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+const browsers = ['chromium', 'firefox', 'webkit'];
+
+for (const browser of browsers) {
+ test(`Test on ${browser}`, async ({ page }) => {
+ qase.title('Browser compatibility test');
+ qase.parameters({ Browser: browser });
+
+ // Test logic
await page.goto('https://example.com');
});
+}
+```
-test('test',
- {
- annotation: { type: 'QaseSuite', description: 'Suite defined in annotation' },
- },
- async ({ page }) => {
- await page.goto('https://example.com');
+### Group Parameters
+
+```typescript
+import { test } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+const testData = [
+ { username: 'user1', password: 'pass1' },
+ { username: 'user2', password: 'pass2' },
+];
+
+for (const data of testData) {
+ test(`Login with ${data.username}`, async ({ page }) => {
+ qase.title('User login test');
+ qase.groupParameters({
+ Username: data.username,
+ Password: data.password,
+ });
+
+ // Test logic
+ await page.goto('https://example.com/login');
});
+}
+```
+
+### Using Playwright Projects as Parameters
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test('Cross-browser test', async ({ page, browserName }) => {
+ qase.title('Browser compatibility test');
+ qase.parameters({ Browser: browserName });
+
+ await page.goto('https://example.com');
+ expect(await page.title()).toBe('Example Domain');
+});
+```
+
+---
+
+## Multi-Project Support
+
+Send test results to multiple Qase projects simultaneously with different test case IDs for each project.
+
+For detailed configuration, examples, and troubleshooting, see the [Multi-Project Support Guide](MULTI_PROJECT.md).
+
+---
+
+## Running Tests
+
+### Basic Execution
+
+```bash
+# Run all tests
+npx playwright test
+
+# Run with Qase reporting enabled (if mode not in config)
+QASE_MODE=testops npx playwright test
+```
+
+### With Environment Variables
+
+```bash
+# Set project and token via environment
+QASE_MODE=testops \
+QASE_TESTOPS_PROJECT=DEMO \
+QASE_TESTOPS_API_TOKEN=your_token \
+npx playwright test
+```
+
+### With Test Plan
+
+```bash
+# Execute tests from a specific test plan
+QASE_MODE=testops \
+QASE_TESTOPS_PLAN_ID=123 \
+npx playwright test
+```
+
+### With Existing Test Run
+
+```bash
+# Report results to an existing test run
+QASE_MODE=testops \
+QASE_TESTOPS_RUN_ID=456 \
+npx playwright test
+```
+
+### Run Specific Tests
+
+```bash
+# Run specific test file
+npx playwright test tests/auth.spec.ts
+
+# Run tests matching pattern
+npx playwright test --grep "login"
+
+# Run tests with specific tag
+npx playwright test --grep "@smoke"
+
+# Run specific project (browser)
+npx playwright test --project=chromium
```
---
-## Selective execution tests
+## Integration Patterns
+
+### Playwright Test Fixtures
+
+```typescript
+import { test as base, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+type MyFixtures = {
+ authenticatedPage: Page;
+};
-You can use the `grep` property to select tests to run. You can specify a regular expression to match the Qase IDs in `playwright.config.js` or in command line arguments.
+const test = base.extend({
+ authenticatedPage: async ({ page }, use) => {
+ await qase.step('Setup authenticated page', async () => {
+ await page.goto('https://example.com/login');
+ await page.fill('#email', 'user@example.com');
+ await page.fill('#password', 'password123');
+ await page.click('button[type="submit"]');
+ });
-### Configuration-based filtering
+ await use(page);
+
+ await qase.step('Cleanup authenticated page', async () => {
+ await page.click('#logout');
+ });
+ },
+});
+
+test(qase(1, 'Test with authenticated page'), async ({ authenticatedPage }) => {
+ await authenticatedPage.goto('https://example.com/dashboard');
+ await expect(authenticatedPage.locator('.dashboard')).toBeVisible();
+});
+```
-```javascript
-const config = {
- grep: /(Qase ID: 1|2|3)/,
+### Page Object Pattern with Qase
+
+```typescript
+import { test, expect, Page } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+class LoginPage {
+ constructor(private page: Page) {}
+
+ async navigate() {
+ await qase.step('Navigate to login page', async () => {
+ await this.page.goto('https://example.com/login');
+ });
+ }
+
+ async login(email: string, password: string) {
+ await qase.step('Enter credentials', async () => {
+ await this.page.fill('#email', email);
+ await this.page.fill('#password', password);
+ });
+
+ await qase.step('Click login button', async () => {
+ await this.page.click('button[type="submit"]');
+ });
+ }
+}
+
+test(qase(1, 'User can login'), async ({ page }) => {
+ const loginPage = new LoginPage(page);
+ await loginPage.navigate();
+ await loginPage.login('user@example.com', 'password123');
+
+ await expect(page.locator('.dashboard')).toBeVisible();
+});
+```
+
+### Parallel Test Execution
+
+```typescript
+import { test } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test.describe.configure({ mode: 'parallel' });
+
+test.describe('Parallel tests', () => {
+ test(qase(1, 'Test 1'), async ({ page }) => {
+ await page.goto('https://example.com');
+ });
+
+ test(qase(2, 'Test 2'), async ({ page }) => {
+ await page.goto('https://example.com/about');
+ });
+
+ test(qase(3, 'Test 3'), async ({ page }) => {
+ await page.goto('https://example.com/contact');
+ });
+});
+```
+
+### Project-Based Test Organization
+
+```typescript
+// playwright.config.ts
+import { defineConfig, devices } from '@playwright/test';
+
+export default defineConfig({
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+ {
+ name: 'firefox',
+ use: { ...devices['Desktop Firefox'] },
+ },
+ {
+ name: 'webkit',
+ use: { ...devices['Desktop Safari'] },
+ },
+ ],
reporter: [
+ ['list'],
[
'playwright-qase-reporter',
{
- debug: true,
-
testops: {
api: {
- token: 'api_key',
+ token: process.env.QASE_API_TOKEN,
},
-
- project: 'project_code',
- uploadAttachments: true,
-
- run: {
- complete: true,
+ project: 'DEMO',
+ },
+ framework: {
+ browser: {
+ addAsParameter: true,
+ parameterName: 'Browser',
},
},
},
],
],
-};
+});
+```
+
+### Using test.describe for Suites
+
+```typescript
+import { test } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test.describe('Authentication', () => {
+ test(qase(1, 'User registration'), async ({ page }) => {
+ qase.suite('Authentication\tRegistration');
+ await page.goto('https://example.com/register');
+ });
+
+ test(qase(2, 'User login'), async ({ page }) => {
+ qase.suite('Authentication\tLogin');
+ await page.goto('https://example.com/login');
+ });
+
+ test(qase(3, 'Password reset'), async ({ page }) => {
+ qase.suite('Authentication\tPassword Reset');
+ await page.goto('https://example.com/reset');
+ });
+});
+```
+
+---
+
+## Common Use Cases
-module.exports = config;
+### Attach Screenshot on Failure
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test(qase(1, 'Test with failure screenshot'), async ({ page }) => {
+ try {
+ await page.goto('https://example.com');
+ await expect(page.locator('.non-existent')).toBeVisible();
+ } catch (error) {
+ const screenshot = await page.screenshot();
+ qase.attach({
+ name: 'failure-screenshot.png',
+ content: screenshot,
+ contentType: 'image/png',
+ });
+ throw error;
+ }
+});
```
-### Command line filtering
+### Report Visual Comparison Results
-```bash
-# Run tests with specific Qase IDs
-npx playwright test --grep "(Qase ID: 1|2|3)"
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test(qase(1, 'Visual regression test'), async ({ page }) => {
+ await page.goto('https://example.com');
+
+ const screenshot = await page.screenshot();
+ qase.attach({
+ name: 'actual-screenshot.png',
+ content: screenshot,
+ contentType: 'image/png',
+ });
+
+ await expect(page).toHaveScreenshot('homepage.png');
+});
```
-### Using qasectl for test plan filtering
+### Use with Multiple Browsers
-If you use `qase([Id], 'Test name')` syntax for test case IDs, you can use `qasectl` for getting prepared regex with all Qase IDs from your test plan. See [qasectl](https://github.com/qase-tms/qasectl/blob/main/docs/command.md#get-filtered-results) for more information.
+```typescript
+import { test, devices } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
-For example, if you have a test plan with ID 123, you can get the regular expression with all Qase IDs from it with the following command:
+for (const browserType of ['chromium', 'firefox', 'webkit']) {
+ test(`Test on ${browserType}`, async ({ playwright }) => {
+ qase.title('Cross-browser compatibility test');
+ qase.parameters({ Browser: browserType });
-```bash
-qasectl testops filter --project PROJ --token --planID 123 --framework playwright --output qase.env --verbose
+ const browser = await playwright[browserType].launch();
+ const page = await browser.newPage();
+
+ await page.goto('https://example.com');
+ await page.close();
+ await browser.close();
+ });
+}
```
-Specify result to run command:
+### Group by test.describe as Qase Suite
-```bash
-npx playwright test --grep "$(cat qase.env | grep QASE_FILTERED_RESULTS | cut -d'=' -f2)"
+```typescript
+import { test } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test.describe('User Management', () => {
+ test.describe('Registration', () => {
+ test(qase(1, 'User can register'), async ({ page }) => {
+ qase.suite('User Management\tRegistration');
+ await page.goto('https://example.com/register');
+ });
+ });
+
+ test.describe('Authentication', () => {
+ test(qase(2, 'User can login'), async ({ page }) => {
+ qase.suite('User Management\tAuthentication');
+ await page.goto('https://example.com/login');
+ });
+ });
+});
```
-Only tests with Qase IDs from the file will be run and reported to Qase.
+### Report API Test Results
+
+```typescript
+import { test, expect, request } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test(qase(201, 'GET /users returns 200'), async () => {
+ qase.suite('API Tests');
+ qase.fields({ layer: 'api' });
+
+ await qase.step('Send GET request', async () => {
+ const context = await request.newContext();
+ const response = await context.get('https://api.example.com/users');
+
+ qase.attach({
+ name: 'response.json',
+ content: JSON.stringify(await response.json(), null, 2),
+ contentType: 'application/json',
+ });
+
+ expect(response.status()).toBe(200);
+ });
+});
+```
+
+---
+
+## Troubleshooting
+
+### Tests Not Appearing in Qase
+
+**Problem:** Tests run successfully but results are not visible in Qase.
+
+**Solutions:**
+
+1. Verify `mode` is set to `TestOps` in playwright.config.ts:
+ ```typescript
+ reporter: [
+ [
+ 'playwright-qase-reporter',
+ { mode: 'TestOps', TestOps: { ... } }
+ ],
+ ]
+ ```
+
+2. Check API token has write permissions:
+ - Go to https://app.qase.io/apps
+ - Regenerate token if needed
+
+3. Verify project code is correct:
+ ```typescript
+ TestOps: {
+ project: 'DEMO', // Check this matches your project
+ }
+ ```
+
+4. Enable debug logging:
+ ```typescript
+ {
+ debug: true,
+ TestOps: { ... }
+ }
+ ```
+
+### Reporter Not Found Error
+
+**Problem:** `Cannot find module 'playwright-qase-reporter'`
+
+**Solutions:**
+
+1. Install the package:
+ ```bash
+ npm install --save-dev playwright-qase-reporter
+ ```
+
+2. Verify playwright.config.ts reporter configuration:
+ ```typescript
+ reporter: [
+ ['playwright-qase-reporter', { /* config */ }],
+ ]
+ ```
+
+### Qase Object Not Available in Tests
+
+**Problem:** `Qase is not defined` or `Cannot read property 'id' of undefined`
+
+**Solutions:**
+
+1. Import Qase at the top of your test file:
+ ```typescript
+ import { qase } from 'playwright-qase-reporter';
+ ```
+
+2. Ensure correct import path:
+ ```typescript
+ // Correct
+ import { qase } from 'playwright-qase-reporter';
+
+ // Incorrect
+ import { qase } from 'playwright-qase-reporter/dist/playwright';
+ ```
+
+### Parallel Execution Issues
+
+**Problem:** Test results not grouped correctly when running in parallel.
+
+**Solutions:**
+
+1. Qase reporter handles parallel execution automatically.
+
+2. If issues persist, check that each test has unique ID or title:
+ ```typescript
+ test(qase(1, 'Unique test name'), async ({ page }) => {});
+ ```
+
+3. Ensure you're using the latest version of the reporter.
+
+### Screenshots Not Attaching
+
+**Problem:** Screenshots are not visible in Qase test results.
+
+**Solutions:**
+
+1. Verify `uploadAttachments` is enabled:
+ ```typescript
+ TestOps: {
+ uploadAttachments: true,
+ }
+ ```
+
+2. Check screenshot buffer is valid:
+ ```typescript
+ const screenshot = await page.screenshot();
+ console.log('Screenshot size:', screenshot.length);
+ qase.attach({
+ name: 'screenshot.png',
+ content: screenshot,
+ contentType: 'image/png',
+ });
+ ```
+
+3. Enable debug logging to see upload status.
+
+### Browser Parameter Not Appearing
+
+**Problem:** Browser name not showing as parameter in Qase.
+
+**Solutions:**
+
+1. Enable browser parameter in config:
+ ```typescript
+ framework: {
+ browser: {
+ addAsParameter: true,
+ parameterName: 'Browser',
+ },
+ }
+ ```
+
+2. Verify you're using Playwright projects with different browsers.
+
+---
+
+## Complete Examples
+
+### Full Test Example
+
+```typescript
+import { test, expect } from '@playwright/test';
+import { qase } from 'playwright-qase-reporter';
+
+test(qase([1, 2], 'Comprehensive test with all features'), async ({ page, browserName }) => {
+ // Set metadata
+ qase.title('User can complete full registration flow');
+ qase.suite('Registration\tEnd-to-End');
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'e2e',
+ description: 'Tests complete user registration flow from start to finish',
+ preconditions: 'Application is running and database is accessible',
+ });
+ qase.parameters({
+ Browser: browserName,
+ Environment: 'staging',
+ });
+
+ // Execute test with steps
+ await qase.step('Navigate to registration page', async () => {
+ await page.goto('https://example.com/register');
+
+ const screenshot = await page.screenshot();
+ qase.attach({
+ name: 'registration-page.png',
+ content: screenshot,
+ contentType: 'image/png',
+ });
+ });
+
+ await qase.step('Fill registration form', async () => {
+ await page.fill('#email', 'user@example.com');
+ await page.fill('#password', 'password123');
+ await page.fill('#confirmPassword', 'password123');
+ });
+
+ await qase.step('Submit form', async () => {
+ await page.click('button[type="submit"]');
+ });
+
+ await qase.step('Verify registration success', async () => {
+ await expect(page.locator('.success-message')).toBeVisible();
+
+ const finalScreenshot = await page.screenshot();
+ qase.attach({
+ name: 'success-page.png',
+ content: finalScreenshot,
+ contentType: 'image/png',
+ });
+ });
+
+ // Add final comment
+ qase.comment('Test completed successfully with all validations passing');
+});
+```
+
+### Example Project Structure
+
+```
+my-project/
+├── qase.config.json
+├── playwright.config.ts
+├── tests/
+│ ├── auth.spec.ts
+│ ├── api.spec.ts
+│ └── e2e.spec.ts
+├── pages/
+│ ├── login.page.ts
+│ └── dashboard.page.ts
+└── package.json
+```
+
+---
+
+## See Also
+
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Attachments Guide](ATTACHMENTS.md)
+- [Steps Guide](STEPS.md)
+- [Multi-Project Support](MULTI_PROJECT.md)
+- [Upgrade Guide](UPGRADE.md)
diff --git a/qase-testcafe/README.md b/qase-testcafe/README.md
index 8a71c0cd..36c97fd1 100644
--- a/qase-testcafe/README.md
+++ b/qase-testcafe/README.md
@@ -1,125 +1,343 @@
-# Qase TMS TestCafe reporter
+# [Qase TestOps](https://qase.io) TestCafe Reporter
-Publish results simple and easy.
+[](https://www.apache.org/licenses/LICENSE-2.0)
+[](https://www.npmjs.com/package/testcafe-qase-reporter)
-To install the latest version, run:
+Qase TestCafe Reporter enables seamless integration between your TestCafe tests and [Qase TestOps](https://qase.io), providing automatic test result reporting, test case management, and comprehensive test analytics.
-```sh
-npm install -D testcafe-reporter-qase
+## Features
+
+- Link automated tests to Qase test cases by ID
+- Auto-create test cases from your test code
+- Report test results with rich metadata (fields, attachments, steps)
+- Support for parameterized tests
+- Multi-project reporting support
+- Flexible configuration (file, environment variables)
+
+## Installation
+
+```bash
+npm install --save-dev testcafe-reporter-qase
```
-## Updating from v1
+## Quick Start
-To update a test project using testcafe-reporter-qaser@v1 to version 2:
+**1. Create `qase.config.json` in your project root:**
-1. Update reporter configuration in `qase.config.json` and/or environment variables —
- see the [configuration reference](#configuration) below.
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "project": "YOUR_PROJECT_CODE",
+ "api": {
+ "token": "YOUR_API_TOKEN"
+ }
+ }
+}
+```
-## Example of usage
+**2. Add Qase ID to your test using metadata:**
-The TestCafe reporter has the ability to auto-generate test cases
-and suites from your test data.
+```javascript
+import { test } from 'testcafe';
+import { qase } from 'testcafe-reporter-qase/qase';
-You can also annotate the tests with the IDs of existing test cases
-from Qase.io before executing tests. It's a more reliable way to bind
-autotests to test cases, that persists when you rename, move, or
-parameterize your tests.
+fixture`Example Fixture`
+ .page`https://example.com`;
-### Metadata
+test.meta(qase.id(1).create())(
+ 'Test with Qase ID',
+ async (t) => {
+ await t.expect(true).ok();
+ }
+);
+```
-- `qase.title` - set the title of the test case
-- `qase.fields` - set the fields of the test case
-- `qase.suite` - set the suite of the test case
-- `qase.comment` - set the comment of the test case
-- `qase.parameters` - set the parameters of the test case
-- `qase.groupParameters` - set the group parameters of the test case
-- `qase.ignore` - ignore the test case in Qase. The test will be executed, but the results will not be sent to Qase.
-- `qase.step` - create a step in the test case
-- `qase.attach` - attach a file or content to the test case
+**3. Run your tests with Qase reporter:**
-For detailed instructions on using annotations and methods, refer to [Usage](docs/usage.md).
+```bash
+QASE_MODE=testops npx testcafe chrome tests/ -r spec,qase
+```
-For example:
+## Configuration
-```js
-const q = qase.id(1)
- .title('Text typing basics')
- .field({ 'severity': 'high' })
- .parameters({ 'browser': 'chrome' })
- .create();
-test.meta({ ...q })(
- 'Click check boxes and then verify their state',
- async (t) => {
- await t;
+The reporter is configured via (in order of priority):
+
+1. **Environment variables** (`QASE_*`)
+2. **Config file** (`qase.config.json`)
+
+### Minimal Configuration
+
+| Option | Environment Variable | Description |
+|--------|---------------------|-------------|
+| `mode` | `QASE_MODE` | Set to `testops` to enable reporting |
+| `testops.project` | `QASE_TESTOPS_PROJECT` | Your Qase project code |
+| `testops.api.token` | `QASE_TESTOPS_API_TOKEN` | Your Qase API token |
+
+### Example `qase.config.json`
+
+```json
+{
+ "mode": "testops",
+ "fallback": "report",
+ "testops": {
+ "project": "YOUR_PROJECT_CODE",
+ "api": {
+ "token": "YOUR_API_TOKEN"
+ },
+ "run": {
+ "title": "TestCafe Automated Run"
+ },
+ "batch": {
+ "size": 100
+ }
},
+ "report": {
+ "driver": "local",
+ "connection": {
+ "local": {
+ "path": "./build/qase-report",
+ "format": "json"
+ }
+ }
+ }
+}
+```
+
+> **Full configuration reference:** See [qase-javascript-commons](../qase-javascript-commons/README.md) for all available options including logging, status mapping, execution plans, and more.
+
+## Usage
+
+### Link Tests with Test Cases
+
+Associate your tests with Qase test cases using the `qase.id()` method:
+
+**Single ID:**
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test.meta(qase.id(1).create())(
+ 'Test with single ID',
+ async (t) => {
+ await t.expect(true).ok();
+ }
);
```
----
+**Multiple IDs:**
+```javascript
+test.meta(qase.id([1, 2, 3]).create())(
+ 'Test linked to multiple cases',
+ async (t) => {
+ await t.expect(true).ok();
+ }
+);
+```
-To run tests and create a test run, execute the command (for example from folder examples):
+### Add Metadata
-```bash
-QASE_MODE=testops npx testcafe chrome test.js -r spec,qase
+Enhance your tests with additional information:
+
+**Custom Title:**
+```javascript
+test.meta(qase.title('Custom test title').create())(
+ 'Test with custom title',
+ async (t) => {
+ await t.expect(true).ok();
+ }
+);
```
-or
+**Fields:**
+```javascript
+test.meta(
+ qase.fields({
+ 'severity': 'critical',
+ 'priority': 'high',
+ 'layer': 'e2e',
+ 'description': 'Verifies critical user flow',
+ }).create()
+)(
+ 'Test with fields',
+ async (t) => {
+ await t.expect(true).ok();
+ }
+);
+```
-```bash
-npm test
+**Combined Metadata:**
+```javascript
+test.meta(
+ qase.id(1)
+ .title('User can login successfully')
+ .fields({ 'severity': 'critical', 'priority': 'high' })
+ .parameters({ 'browser': 'chrome', 'environment': 'staging' })
+ .create()
+)(
+ 'Login test',
+ async (t) => {
+ await t.typeText('#email', 'user@example.com');
+ await t.typeText('#password', 'password123');
+ await t.click('#login-button');
+ await t.expect('#dashboard').exists;
+ }
+);
```
-
-
-
+### Add Steps
+
+Create detailed test steps for better reporting:
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test('Test with steps', async (t) => {
+ await qase.step('Navigate to login page', async () => {
+ await t.navigateTo('https://example.com/login');
+ });
+
+ await qase.step('Enter credentials', async (s1) => {
+ await s1.step('Type email', async () => {
+ await t.typeText('#email', 'user@example.com');
+ });
-A test run will be performed and available at:
+ await s1.step('Type password', async () => {
+ await t.typeText('#password', 'password123');
+ });
+ });
+ await qase.step('Submit form', async () => {
+ await t.click('#login-button');
+ });
+});
```
-https://app.qase.io/run/QASE_PROJECT_CODE
+
+### Attach Files
+
+Attach screenshots or other files to test results:
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test('Test with attachments', async (t) => {
+ const screenshot = await t.takeScreenshot();
+
+ qase.attach({
+ name: 'screenshot.png',
+ content: screenshot,
+ contentType: 'image/png',
+ });
+
+ qase.attach({ paths: '/path/to/log.txt' });
+ qase.attach({ paths: ['/path/to/file1.txt', '/path/to/file2.log'] });
+});
```
-
-
-
+### Ignore Tests
-## Configuration
+Exclude specific tests from Qase reporting (test still runs):
+
+```javascript
+test.meta(qase.ignore().create())(
+ 'Test ignored in Qase',
+ async (t) => {
+ await t.expect(true).ok();
+ }
+);
+```
+
+### Test Result Statuses
-Qase Testcafe reporter can be configured in multiple ways:
+| TestCafe Result | Qase Status |
+|-----------------|-------------|
+| passed | passed |
+| failed | failed |
+| skipped | skipped |
-- using a separate config file `qase.config.json`,
-- using environment variables (they override the values from the configuration files).
+> For more usage examples, see the [Usage Guide](docs/usage.md).
-For a full list of configuration options, see
-the [Configuration reference](../qase-javascript-commons/README.md#configuration).
+## Running Tests
-Example `qase.config.json` file:
+### Basic Execution
+
+```bash
+# Run all tests with Qase reporter
+QASE_MODE=testops npx testcafe chrome tests/ -r spec,qase
+
+# Run specific test file
+QASE_MODE=testops npx testcafe chrome tests/login.test.js -r qase
+
+# Run in headless mode
+QASE_MODE=testops npx testcafe chrome:headless tests/ -r qase
+```
+
+### Multiple Browsers
+
+```bash
+# Run in multiple browsers
+QASE_MODE=testops npx testcafe chrome,firefox tests/ -r qase
+
+# Run in all installed browsers
+QASE_MODE=testops npx testcafe all tests/ -r qase
+```
+
+### Environment Variables
+
+```bash
+# Override configuration with environment variables
+QASE_MODE=testops \
+QASE_TESTOPS_PROJECT=DEMO \
+QASE_TESTOPS_API_TOKEN=your_token \
+npx testcafe chrome tests/ -r qase
+```
+
+### With TestCafe Configuration File
+
+Create `.testcaferc.json`:
```json
{
- "mode": "testops",
- "debug": true,
- "testops": {
- "api": {
- "token": "api_key"
+ "browsers": ["chrome:headless"],
+ "src": ["tests/**/*.test.js"],
+ "reporter": [
+ {
+ "name": "spec"
},
- "project": "project_code",
- "run": {
- "complete": true
+ {
+ "name": "qase"
}
- }
+ ],
+ "concurrency": 3,
+ "quarantineMode": false
}
```
-Check out the example of configuration for multiple reporters in the
-[demo project](../examples/testcafe/qase.config.json).
+Then run:
+
+```bash
+QASE_MODE=testops npx testcafe
+```
## Requirements
-We maintain the reporter on [LTS versions of Node](https://nodejs.org/en/about/releases/).
+- Node.js >= 14
+- TestCafe >= 2.0.0
+
+## Documentation
+
+| Guide | Description |
+|-------|-------------|
+| [Usage Guide](docs/usage.md) | Complete usage reference with all methods and options |
+| [Attachments](docs/ATTACHMENTS.md) | Adding screenshots, logs, and files to test results |
+| [Steps](docs/STEPS.md) | Defining test steps for detailed reporting |
+| [Multi-Project Support](docs/MULTI_PROJECT.md) | Reporting to multiple Qase projects |
+| [Upgrade Guide](docs/UPGRADE.md) | Migration guide for breaking changes |
+| [Configuration Reference](../qase-javascript-commons/README.md) | Full configuration options |
+
+## Examples
-`testcafe >= 2.0.0`
+See the [examples directory](../examples/single/testcafe/) for complete working examples.
-
+## License
-[auth]: https://developers.qase.io/#authentication
+Apache License 2.0. See [LICENSE](LICENSE) for details.
diff --git a/qase-testcafe/docs/ATTACHMENTS.md b/qase-testcafe/docs/ATTACHMENTS.md
new file mode 100644
index 00000000..6cac1d56
--- /dev/null
+++ b/qase-testcafe/docs/ATTACHMENTS.md
@@ -0,0 +1,344 @@
+# Attachments in TestCafe
+
+This guide covers how to attach files, screenshots, logs, and other content to your Qase test results.
+
+---
+
+## Overview
+
+Qase TestCafe Reporter supports attaching various types of content to test results:
+
+- **Files** — Attach files from the filesystem
+- **Screenshots** — Attach images captured during test execution
+- **Logs** — Attach text logs or console output
+- **Binary data** — Attach any binary content from memory
+
+Attachments can be added to:
+- **Test cases** — Visible in the overall test result
+- **Test steps** — Visible in specific step results
+
+---
+
+## Attaching Files
+
+### From File Path
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test('Test with file attachment', async (t) => {
+ qase.attach({ paths: ['path/to/file.txt'] });
+ await t.expect(true).ok();
+});
+```
+
+### Multiple Files
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test('Test with multiple file attachments', async (t) => {
+ qase.attach({ paths: ['path/to/file1.txt', 'path/to/file2.log'] });
+ await t.expect(true).ok();
+});
+```
+
+---
+
+## Attaching Content from Memory
+
+### Text Content
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test('Test with text content attachment', async (t) => {
+ const logContent = 'Test execution log content';
+
+ qase.attach({
+ name: 'execution.log',
+ content: logContent,
+ type: 'text/plain',
+ });
+
+ await t.expect(true).ok();
+});
+```
+
+### Binary Content (Screenshots)
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test('Test with screenshot attachment', async (t) => {
+ await t.navigateTo('https://example.com');
+
+ const screenshot = await t.takeScreenshot();
+
+ qase.attach({
+ name: 'page-screenshot.png',
+ content: screenshot,
+ type: 'image/png',
+ });
+
+ await t.expect('#header').exists;
+});
+```
+
+### JSON Data
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test('Test with JSON data attachment', async (t) => {
+ const testData = {
+ userId: 123,
+ username: 'testuser',
+ timestamp: new Date().toISOString(),
+ };
+
+ qase.attach({
+ name: 'test-data.json',
+ content: JSON.stringify(testData, null, 2),
+ type: 'application/json',
+ });
+
+ await t.expect(true).ok();
+});
+```
+
+---
+
+## Attaching to Steps
+
+Attach content to a specific test step:
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test('Test with step-level attachments', async (t) => {
+ await qase.step('Navigate and capture screenshot', async (s) => {
+ await t.navigateTo('https://example.com/login');
+
+ s.attach({
+ name: 'login-page.png',
+ content: await t.takeScreenshot(),
+ type: 'image/png',
+ });
+ });
+
+ await qase.step('Perform action', async (s) => {
+ await t.typeText('#username', 'testuser');
+
+ s.attach({
+ name: 'action-log.txt',
+ content: 'Username entered successfully',
+ type: 'text/plain',
+ });
+ });
+});
+```
+
+---
+
+## Method Reference
+
+### `qase.attach()`
+
+Attach content to the test case.
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `paths` | `string[]` | No* | Array of path(s) to file(s) to attach |
+| `content` | `string` or `Buffer` | No* | Content to attach |
+| `name` | `string` | No | Custom filename (auto-detected from path) |
+| `type` | `string` | No | MIME type (auto-detected from extension) |
+
+\* Either `paths` or `content` must be provided, but not both.
+
+**Basic usage:**
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+// Attach file by path
+qase.attach({ paths: ['path/to/file.txt'] });
+
+// Attach content from memory
+qase.attach({
+ name: 'log.txt',
+ content: 'Test log content',
+ type: 'text/plain',
+});
+```
+
+### Step-level Attachment
+
+Within a step callback, use the step parameter:
+
+```javascript
+await qase.step('Step name', async (s) => {
+ s.attach({
+ name: 'step-attachment.txt',
+ content: 'Content for this step',
+ type: 'text/plain',
+ });
+});
+```
+
+---
+
+## MIME Types
+
+Common MIME types are auto-detected based on file extension:
+
+| Extension | MIME Type |
+|-----------|-----------|
+| `.png` | `image/png` |
+| `.jpg`, `.jpeg` | `image/jpeg` |
+| `.gif` | `image/gif` |
+| `.svg` | `image/svg+xml` |
+| `.txt` | `text/plain` |
+| `.log` | `text/plain` |
+| `.json` | `application/json` |
+| `.xml` | `application/xml` |
+| `.html` | `text/html` |
+| `.csv` | `text/csv` |
+| `.pdf` | `application/pdf` |
+| `.zip` | `application/zip` |
+
+For other file types, specify `type` explicitly.
+
+---
+
+## Common Use Cases
+
+### TestCafe Screenshots
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test('Test with TestCafe screenshot', async (t) => {
+ await t.navigateTo('https://example.com');
+
+ // Take screenshot using TestCafe
+ const screenshot = await t.takeScreenshot();
+
+ qase.attach({
+ name: 'page-state.png',
+ content: screenshot,
+ type: 'image/png',
+ });
+
+ await t.expect('#main-content').exists;
+});
+```
+
+### Capturing Page Source
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+import { ClientFunction } from 'testcafe';
+
+test('Test with page source attachment', async (t) => {
+ await t.navigateTo('https://example.com');
+
+ // Get page HTML
+ const getPageHTML = ClientFunction(() => document.documentElement.outerHTML);
+ const pageSource = await getPageHTML();
+
+ qase.attach({
+ name: 'page-source.html',
+ content: pageSource,
+ type: 'text/html',
+ });
+
+ await t.expect('#header').exists;
+});
+```
+
+### Attaching on Test Failure
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test('Test with failure screenshot', async (t) => {
+ try {
+ await t.navigateTo('https://example.com');
+ await t.expect('#nonexistent-element').exists;
+ } catch (error) {
+ // Capture screenshot on failure
+ qase.attach({
+ name: 'failure-screenshot.png',
+ content: await t.takeScreenshot(),
+ type: 'image/png',
+ });
+
+ throw error;
+ }
+});
+```
+
+### Browser Console Logs
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+import { RequestLogger } from 'testcafe';
+
+const logger = RequestLogger(/.*/, {
+ logRequestHeaders: true,
+ logResponseHeaders: true,
+});
+
+fixture`API Tests`
+ .page`https://example.com`
+ .requestHooks(logger);
+
+test('Test with request logs', async (t) => {
+ await t.navigateTo('https://example.com');
+
+ // Attach logged requests
+ qase.attach({
+ name: 'request-log.json',
+ content: JSON.stringify(logger.requests, null, 2),
+ type: 'application/json',
+ });
+
+ await t.expect(logger.requests.length).gt(0);
+});
+```
+
+---
+
+## Troubleshooting
+
+### Attachments Not Appearing
+
+1. Verify the file path exists and is readable
+2. Check file permissions
+3. Enable debug logging to see upload status:
+ ```json
+ {
+ "debug": true
+ }
+ ```
+
+### Large Files
+
+Large attachments may slow down test execution. Consider:
+- Compressing logs before attaching
+- Using selective logging (e.g., only on failure)
+- Setting reasonable size limits
+
+### Binary Data Issues
+
+When attaching binary data, always specify:
+- `name` with appropriate extension
+- `type` if extension doesn't match content type
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Steps Guide](STEPS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-testcafe/docs/MULTI_PROJECT.md b/qase-testcafe/docs/MULTI_PROJECT.md
new file mode 100644
index 00000000..ae22d7a6
--- /dev/null
+++ b/qase-testcafe/docs/MULTI_PROJECT.md
@@ -0,0 +1,208 @@
+# Multi-Project Support in TestCafe
+
+Qase TestCafe Reporter supports sending test results to multiple Qase projects simultaneously. This feature allows you to report the same test execution to different projects with different test case IDs, which is useful when:
+
+* You need to report the same test to different projects
+* Different projects track the same functionality with different test case IDs
+* You want to maintain separate test runs for different environments or teams
+
+---
+
+## Configuration
+
+For detailed configuration options, refer to the [qase-javascript-commons README](../../qase-javascript-commons/README.md#multi-project-support).
+
+### Basic Multi-Project Configuration
+
+Set `mode` to `testops_multi` in your `qase.config.json` and add the `testops_multi` section with `default_project` and `projects`.
+
+**Example configuration:**
+
+```json
+{
+ "mode": "testops_multi",
+ "testops_multi": {
+ "default_project": "PROJ1",
+ "projects": [
+ {
+ "code": "PROJ1",
+ "api": {
+ "token": "your_api_token_for_proj1"
+ }
+ },
+ {
+ "code": "PROJ2",
+ "api": {
+ "token": "your_api_token_for_proj2"
+ }
+ }
+ ]
+ }
+}
+```
+
+---
+
+## Using `qase.projects(mapping).create()`
+
+TestCafe uses a builder pattern for setting test metadata. Use `qase.projects(mapping)` to link a test to multiple projects, then call `.create()` to build the metadata object. Pass the result to `test.meta()`:
+
+```javascript
+import { test } from 'testcafe';
+import { qase } from 'testcafe-reporter-qase/qase';
+
+fixture`Multi-project example`.page`https://example.com`;
+
+// Multi-project test - map to PROJ1 case 100 and PROJ2 case 200
+const q1 = qase.projects({ PROJ1: [100], PROJ2: [200] }).create();
+test.meta(q1)('Login flow reported to two projects', async (t) => {
+ await t.expect(true).ok();
+});
+
+// Multiple IDs per project
+const q2 = qase.projects({ PROJ1: [10, 11], PROJ2: [20] }).create();
+test.meta(q2)('Checkout reported to multiple cases', async (t) => {
+ await t.expect(1 + 1).eql(2);
+});
+```
+
+### Combining with Other Metadata
+
+You can chain `qase.projects()` with other builder methods:
+
+```javascript
+// Combine multi-project with test ID and fields
+const q = qase.id(1).projects({ PROJ1: [100], PROJ2: [200] }).fields({ severity: 'high' }).create();
+test.meta(q)('Test with ID, multi-project, and fields', async (t) => {
+ await t.expect(true).ok();
+});
+```
+
+**Key points:**
+
+- Single project with single ID: `qase.id(100).create()` (reports to default_project)
+- Multi-project: `qase.projects({ PROJ1: [100], PROJ2: [200] }).create()`
+- Multiple IDs per project: `qase.projects({ PROJ1: [10, 11], PROJ2: [20] }).create()`
+- **Always call `.create()` at the end** to build the metadata object
+
+Project codes (e.g. `PROJ1`, `PROJ2`) must match `testops_multi.projects[].code` in your config.
+
+---
+
+## Tests Without Project Mapping
+
+Tests that do not use `qase.projects()` are sent to the `default_project`. If they use `qase.id(id).create()` (single-project syntax), that ID is used for the default project.
+
+```javascript
+// This test goes to default_project with case ID 50
+const q = qase.id(50).create();
+test.meta(q)('Single project test', async (t) => {
+ await t.expect(true).ok();
+});
+
+// This test goes to default_project without a case ID
+test('Test without Qase metadata', async (t) => {
+ await t.expect(true).ok();
+});
+```
+
+---
+
+## Important Notes
+
+1. **Project codes must match**: Codes in `qase.projects({ PROJ1: [1], ... })` must match `testops_multi.projects[].code` in config.
+2. **Mode**: Set `mode` to `testops_multi` in qase.config.json.
+3. **Builder pattern**: Always call `.create()` after setting metadata (e.g., `qase.projects({...}).create()`).
+4. **API tokens**: Each project in `testops_multi.projects[]` can have its own API token for separate authentication.
+
+---
+
+## Examples
+
+See the [multi-project TestCafe example](../../examples/multiProject/testcafe/) for a complete runnable setup.
+
+### Complete Example
+
+Here's a complete TestCafe test file showing multi-project usage:
+
+```javascript
+import { test } from 'testcafe';
+import { qase } from 'testcafe-reporter-qase/qase';
+
+fixture`Multi-project test suite`.page`https://devexpress.github.io/testcafe/example/`;
+
+// Test reported to two projects
+const q1 = qase.projects({ PROJ1: [1], PROJ2: [2] }).create();
+test.meta(q1)('User can submit form', async (t) => {
+ await t
+ .typeText('#developer-name', 'John Doe')
+ .click('#tried-test-cafe')
+ .click('#submit-button');
+
+ await t.expect(true).ok();
+});
+
+// Test with multiple case IDs per project
+const q2 = qase.projects({ PROJ1: [10, 11], PROJ2: [20] }).create();
+test.meta(q2)('Form validation works correctly', async (t) => {
+ await t.click('#submit-button');
+ // Validation error should appear
+ await t.expect(true).ok();
+});
+
+// Test combining multi-project with other metadata
+const q3 = qase
+ .projects({ PROJ1: [100], PROJ2: [200] })
+ .title('Enhanced form test')
+ .fields({ severity: 'critical', priority: 'high' })
+ .create();
+test.meta(q3)('Complete user flow', async (t) => {
+ await t
+ .typeText('#developer-name', 'Jane Smith')
+ .click('#tried-test-cafe')
+ .click('#submit-button');
+});
+
+// Single-project test (uses default_project)
+const q4 = qase.id(50).create();
+test.meta(q4)('Test reported to default project', async (t) => {
+ await t.expect(1 + 1).eql(2);
+});
+
+// Test without Qase metadata (goes to default_project without case ID)
+test('Regular test without Qase tracking', async (t) => {
+ await t.expect(true).ok();
+});
+```
+
+---
+
+## Troubleshooting
+
+### Results Not Appearing in All Projects
+
+* Verify `mode` is `testops_multi` (not `testops`) in qase.config.json
+* Check that project codes in `qase.projects()` match config codes exactly (case-sensitive)
+* Ensure each project has a valid API token with write permissions
+* Confirm you called `.create()` at the end of the builder chain
+
+### Wrong Test Cases Linked
+
+* Verify the mapping object has correct project codes as keys
+* Check that test case IDs exist in the respective projects
+* Enable debug logging to see how the reporter parses multi-project markers
+* Ensure the metadata is passed to `test.meta()` correctly
+
+### Default Project Not Working
+
+* Ensure `default_project` is set in `testops_multi` config
+* Verify the default project code matches one of the projects in the `projects` array
+* Tests without `qase.projects()` will only report to the default project
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Examples](../../examples/multiProject/testcafe/)
diff --git a/qase-testcafe/docs/STEPS.md b/qase-testcafe/docs/STEPS.md
new file mode 100644
index 00000000..e90974b7
--- /dev/null
+++ b/qase-testcafe/docs/STEPS.md
@@ -0,0 +1,331 @@
+# Test Steps in TestCafe
+
+This guide covers how to define and report test steps for detailed execution tracking in Qase.
+
+---
+
+## Overview
+
+Test steps provide granular visibility into test execution. Each step is reported separately, showing:
+
+- Step name and description
+- Step status (passed/failed)
+- Step duration
+- Attachments (if any)
+- Error details (on failure)
+
+---
+
+## Defining Steps
+
+### Using Async Function
+
+Define steps as async functions with callbacks:
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test('Test with steps', async (t) => {
+ await qase.step('Navigate to login page', async () => {
+ await t.navigateTo('https://example.com/login');
+ });
+
+ await qase.step('Enter credentials', async () => {
+ await t.typeText('#email', 'user@example.com');
+ await t.typeText('#password', 'password123');
+ });
+
+ await qase.step('Submit form', async () => {
+ await t.click('#login-button');
+ });
+
+ await qase.step('Verify login success', async () => {
+ await t.expect('#dashboard').exists;
+ });
+});
+```
+
+### Step Parameters
+
+Steps can include parameters for dynamic naming:
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test('Test with parameterized steps', async (t) => {
+ const username = 'testuser';
+ const email = 'user@example.com';
+
+ await qase.step(`Login as ${username}`, async () => {
+ await t.typeText('#email', email);
+ await t.typeText('#password', 'password123');
+ await t.click('#login-button');
+ });
+
+ await qase.step(`Verify ${username} is logged in`, async () => {
+ await t.expect('#user-profile').innerText).contains(username);
+ });
+});
+```
+
+---
+
+## Nested Steps
+
+Create hierarchical step structures using the step callback parameter:
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test('Test with nested steps', async (t) => {
+ await qase.step('Complete user registration', async (s1) => {
+ await s1.step('Fill personal information', async (s2) => {
+ await s2.step('Enter name', async () => {
+ await t.typeText('#firstName', 'John');
+ await t.typeText('#lastName', 'Doe');
+ });
+
+ await s2.step('Enter email', async () => {
+ await t.typeText('#email', 'john.doe@example.com');
+ });
+ });
+
+ await s1.step('Fill address information', async () => {
+ await t.typeText('#address', '123 Main St');
+ await t.typeText('#city', 'New York');
+ });
+
+ await s1.step('Submit registration form', async () => {
+ await t.click('#submit-button');
+ });
+ });
+
+ await qase.step('Verify registration success', async () => {
+ await t.expect('#success-message').exists;
+ });
+});
+```
+
+**Note:** TestCafe nested steps use the step callback parameter (s, s1, s2, etc.) for nesting, not `qase.step()` directly.
+
+---
+
+## Steps with Attachments
+
+Attach content to a specific step:
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test('Test with step attachments', async (t) => {
+ await qase.step('Navigate to page', async (s) => {
+ await t.navigateTo('https://example.com');
+
+ s.attach({
+ name: 'page-loaded.png',
+ content: await t.takeScreenshot(),
+ type: 'image/png',
+ });
+ });
+
+ await qase.step('Perform action', async (s) => {
+ await t.click('#action-button');
+
+ s.attach({
+ name: 'action-log.txt',
+ content: 'Button clicked successfully',
+ type: 'text/plain',
+ });
+ });
+});
+```
+
+---
+
+## Step Status
+
+Steps automatically inherit status from execution:
+
+| Execution | Step Status |
+|-----------|-------------|
+| Completes normally | Passed |
+| Throws Error/AssertionError | Failed |
+| Other exception | Invalid |
+
+---
+
+## Best Practices
+
+### Keep Steps Atomic
+
+Each step should represent a single action:
+
+```javascript
+// Good: One action per step
+await qase.step('Click login button', async () => {
+ await t.click('#login-btn');
+});
+
+await qase.step('Enter username', async () => {
+ await t.typeText('#username', 'user');
+});
+
+// Avoid: Multiple actions in one step
+await qase.step('Fill form and submit', async () => { // Too broad
+ await t.typeText('#username', 'user');
+ await t.typeText('#password', 'pass');
+ await t.click('#submit');
+});
+```
+
+### Use Descriptive Names
+
+```javascript
+// Good: Clear action description
+await qase.step('Verify user is redirected to dashboard', async () => {
+ await t.expect(window.location.href).contains('/dashboard');
+});
+
+// Avoid: Vague names
+await qase.step('Check page', async () => {
+ await t.expect(window.location.href).contains('/dashboard');
+});
+```
+
+### Include Context in Step Names
+
+```javascript
+// Good: Include relevant context
+const productName = 'Laptop';
+await qase.step(`Add product '${productName}' to cart`, async () => {
+ await t.click(`#product-${productName} .add-to-cart`);
+});
+
+// Better than generic:
+await qase.step('Add product', async () => {
+ await t.click(`#product-${productName} .add-to-cart`);
+});
+```
+
+---
+
+## Common Patterns
+
+### Fixture-level Organization
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+fixture`Shopping Cart Tests`
+ .page`https://example.com/shop`;
+
+test('Add item to cart', async (t) => {
+ await qase.step('Browse products', async () => {
+ await t.navigateTo('/products');
+ await t.expect('.product-list').exists;
+ });
+
+ await qase.step('Select product', async () => {
+ await t.click('#product-1 .view-details');
+ });
+
+ await qase.step('Add to cart', async () => {
+ await t.click('#add-to-cart-button');
+ await t.expect('.cart-badge').innerText).eql('1');
+ });
+});
+```
+
+### TestCafe Selector Steps
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test('Test with selector-based steps', async (t) => {
+ await qase.step('Verify header elements', async (s1) => {
+ await s1.step('Check logo is visible', async () => {
+ await t.expect('.header-logo').exists;
+ });
+
+ await s1.step('Check navigation menu', async () => {
+ await t.expect('.nav-menu').exists;
+ await t.expect('.nav-menu li').count).gte(3);
+ });
+ });
+
+ await qase.step('Interact with elements', async () => {
+ await t.hover('.dropdown-trigger');
+ await t.click('.dropdown-menu .first-item');
+ });
+});
+```
+
+### Setup/Teardown Steps
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test('Test with setup and teardown', async (t) => {
+ await qase.step('Setup: Create test data', async () => {
+ // Create test user, seed database, etc.
+ await t.eval(() => {
+ localStorage.setItem('testData', JSON.stringify({ userId: 123 }));
+ });
+ });
+
+ await qase.step('Execute main test flow', async () => {
+ await t.navigateTo('/dashboard');
+ await t.expect('#user-id').innerText).eql('123');
+ });
+
+ await qase.step('Teardown: Clean up test data', async () => {
+ await t.eval(() => {
+ localStorage.removeItem('testData');
+ });
+ });
+});
+```
+
+---
+
+## Troubleshooting
+
+### Steps Not Appearing
+
+1. Verify the step function is properly imported from `testcafe-reporter-qase/qase`
+2. Check that steps are executed within a test context
+3. Enable debug logging to trace step recording
+4. Ensure you're using `await` with async step callbacks
+
+### Nested Steps Flattened
+
+Ensure you're using the callback parameter correctly for nesting:
+
+```javascript
+// Correct: Nested via callback parameter
+await qase.step('Parent step', async (s1) => {
+ await s1.step('Child step', async () => {
+ // Child step logic
+ });
+});
+
+// Incorrect: This creates sequential steps, not nested
+await qase.step('Step 1', async () => {
+ // Step 1 logic
+});
+await qase.step('Step 2', async () => { // Not nested under Step 1
+ // Step 2 logic
+});
+```
+
+### Step Duration Shows 0
+
+Steps need measurable execution time. Very fast steps may show 0ms duration.
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Attachments Guide](ATTACHMENTS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-testcafe/docs/UPGRADE.md b/qase-testcafe/docs/UPGRADE.md
new file mode 100644
index 00000000..310d8859
--- /dev/null
+++ b/qase-testcafe/docs/UPGRADE.md
@@ -0,0 +1,413 @@
+# Upgrade Guide: TestCafe Reporter
+
+This guide covers migration steps between major versions of the Qase TestCafe Reporter.
+
+---
+
+## Version History
+
+| Version | Release Date | Node.js Support | Key Changes |
+|---------|--------------|-----------------|-------------|
+| 2.2.0 | January 2026 | >= 14 | Current stable release with improved metadata support |
+| 2.1.0 | December 2025 | >= 14 | Enhanced builder pattern and multi-project support |
+| 2.0.0 | August 2025 | >= 14 | Complete rewrite with new architecture |
+
+---
+
+## Upgrading to 2.x
+
+### Breaking Changes
+
+The testcafe-reporter-qase started with the v2.x architecture, leveraging the unified qase-javascript-commons library for consistent reporting across all test frameworks. If you are using v2.x, you are already on the latest architecture.
+
+**No migration from a previous major version is required for testcafe-reporter-qase.**
+
+### Current Version Features
+
+Version 2.2.0 includes:
+
+- Full support for Qase TestOps API with batch result upload
+- Builder pattern for test metadata: `.meta(qase.id(1).create())`
+- Rich metadata support: titles, fields, suites, parameters, comments
+- Nested test steps with callback-based API
+- File and content-based attachments
+- Multi-project support for reporting to multiple Qase projects
+- Flexible configuration via `qase.config.json` or environment variables
+
+---
+
+## Configuration
+
+### Current Format (v2.x)
+
+Configuration uses the modern qase-javascript-commons format:
+
+**qase.config.json:**
+
+```json
+{
+ "mode": "testops",
+ "debug": false,
+ "testops": {
+ "api": {
+ "token": "api_token_here"
+ },
+ "project": "DEMO",
+ "run": {
+ "title": "TestCafe Automated Run",
+ "description": "Test run from CI/CD pipeline",
+ "complete": true
+ },
+ "batch": {
+ "size": 100
+ }
+ }
+}
+```
+
+**Environment Variables:**
+
+```bash
+export QASE_MODE=testops
+export QASE_TESTOPS_API_TOKEN=your_api_token
+export QASE_TESTOPS_PROJECT=DEMO
+```
+
+**Command Line:**
+
+```bash
+QASE_MODE=testops npx testcafe chrome tests/ --reporter qase
+```
+
+---
+
+## Import Pattern
+
+### Current Import (v2.x)
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+```
+
+**CommonJS:**
+
+```javascript
+const { qase } = require('testcafe-reporter-qase/qase');
+```
+
+**Note:** TestCafe uses the `/qase` subpath to access the builder API.
+
+---
+
+## API Reference
+
+### Test Case ID Linking with Builder Pattern
+
+TestCafe uses a builder pattern with `.meta()` for test metadata:
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+fixture('User Authentication')
+ .page('https://example.com');
+
+test
+ .meta(qase.id(1).create())
+ ('User can login with valid credentials', async (t) => {
+ await t
+ .typeText('#username', 'user@example.com')
+ .typeText('#password', 'password123')
+ .click('#login-button')
+ .expect(Selector('.dashboard').exists).ok();
+ });
+
+// Multiple test case IDs
+test
+ .meta(qase.id(1, 2, 3).create())
+ ('Test with multiple IDs', async (t) => {
+ // Test code
+ });
+```
+
+### Metadata Methods
+
+```javascript
+test
+ .meta(
+ qase
+ .id(1)
+ .title('Custom test title')
+ .fields({ severity: 'critical', priority: 'high', layer: 'e2e' })
+ .suite('Authentication / Login')
+ .create()
+ )
+ ('Test with rich metadata', async (t) => {
+ // Test code
+ });
+```
+
+**Builder Pattern Methods:**
+
+- `.id(...ids)` - Set test case ID(s)
+- `.title(string)` - Set custom title
+- `.fields(object)` - Set custom fields
+- `.suite(string)` - Set suite hierarchy
+- `.create()` - **Required** - Finalize and create metadata object
+
+### Steps with Nested Callbacks
+
+TestCafe steps use a callback-based pattern with step parameters:
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test
+ .meta(qase.id(1).create())
+ ('Test with steps', async (t) => {
+ await qase.step(t, 'Navigate to login page', async (s) => {
+ await s.navigateTo('https://example.com/login');
+ });
+
+ await qase.step(t, 'Enter credentials', async (s) => {
+ await s.typeText('#username', 'user@example.com');
+ await s.typeText('#password', 'password123');
+ });
+
+ await qase.step(t, 'Verify login success', async (s) => {
+ await s.expect(Selector('.dashboard').exists).ok();
+ });
+ });
+```
+
+**Nested Steps:**
+
+```javascript
+await qase.step(t, 'Parent step', async (s) => {
+ await qase.step(s, 'Child step 1', async (s1) => {
+ // Use s1 for TestCafe controller
+ await s1.typeText('#field1', 'value1');
+ });
+
+ await qase.step(s, 'Child step 2', async (s2) => {
+ await s2.typeText('#field2', 'value2');
+ });
+});
+```
+
+**Note:** The step callback parameter (`s`, `s1`, `s2`) is the TestCafe controller for that step scope.
+
+### Attachments
+
+**Path-based Attachment:**
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test
+ .meta(qase.id(1).create())
+ ('Test with attachment', async (t) => {
+ // Test code
+ await qase.attach(t, { paths: '/path/to/log.txt' });
+ });
+```
+
+**Content-based Attachment:**
+
+```javascript
+await qase.attach(t, {
+ name: 'test-data.json',
+ content: JSON.stringify({ key: 'value' }),
+ type: 'application/json', // Note: 'type' not 'contentType'
+});
+```
+
+**Screenshot Attachment:**
+
+```javascript
+await qase.attach(t, {
+ name: 'screenshot.png',
+ content: await t.takeScreenshot(),
+ type: 'image/png',
+});
+```
+
+**Important:** TestCafe uses `type` parameter (not `contentType`) for attachment MIME type.
+
+---
+
+## Running Tests
+
+### Command Line Usage
+
+**Basic:**
+
+```bash
+QASE_MODE=testops npx testcafe chrome tests/ --reporter qase
+```
+
+**With Custom Run Title:**
+
+```bash
+QASE_MODE=testops \
+QASE_TESTOPS_RUN_TITLE="E2E Tests - Chrome" \
+npx testcafe chrome tests/ --reporter qase
+```
+
+**Multiple Browsers:**
+
+```bash
+QASE_MODE=testops npx testcafe chrome,firefox tests/ --reporter qase
+```
+
+**Combined with other reporters:**
+
+```bash
+QASE_MODE=testops npx testcafe chrome tests/ --reporter spec,qase
+```
+
+---
+
+## Compatibility Notes
+
+### Node.js Version Support
+
+- **Current (2.2.0):** Node.js >= 14
+
+### TestCafe Version Support
+
+- **Current (2.2.0):** TestCafe >= 2.0.0
+- Tested with TestCafe 3.x
+
+### Framework Compatibility
+
+- ES Modules recommended
+- CommonJS supported
+- TypeScript support with full type definitions
+- Works with TestCafe's Page Object Model
+- Compatible with TestCafe Studio
+- Supports parallel test execution
+
+---
+
+## Troubleshooting
+
+### Common Issues
+
+#### Issue: Builder pattern not recognized
+
+**Solution:** Ensure you're calling `.create()` at the end of the builder chain:
+
+```javascript
+// Correct
+test.meta(qase.id(1).create())('Test name', async (t) => { /* ... */ });
+
+// Incorrect - missing .create()
+test.meta(qase.id(1))('Test name', async (t) => { /* ... */ });
+```
+
+#### Issue: Steps not working
+
+**Solution:** Pass the TestCafe controller (`t`) as the first parameter:
+
+```javascript
+// Correct
+await qase.step(t, 'Step name', async (s) => {
+ await s.click('#button');
+});
+
+// Incorrect - missing t parameter
+await qase.step('Step name', async (s) => {
+ await s.click('#button');
+});
+```
+
+#### Issue: Attachments not appearing
+
+**Solution:** Use `type` parameter (not `contentType`):
+
+```javascript
+// Correct
+await qase.attach(t, {
+ name: 'file.txt',
+ content: 'content',
+ type: 'text/plain',
+});
+
+// Incorrect - contentType not supported in TestCafe
+await qase.attach(t, {
+ name: 'file.txt',
+ content: 'content',
+ contentType: 'text/plain',
+});
+```
+
+Also ensure you're passing `t` as first parameter:
+
+```javascript
+// Correct
+await qase.attach(t, { paths: 'file.txt' });
+
+// Incorrect - missing t parameter
+await qase.attach({ paths: 'file.txt' });
+```
+
+#### Issue: Reporter not running
+
+**Solution:** Verify the reporter flag and environment variables:
+
+```bash
+# Check reporter is specified
+npx testcafe chrome tests/ --reporter qase
+
+# Check QASE_MODE is set
+QASE_MODE=testops npx testcafe chrome tests/ --reporter qase
+```
+
+#### Issue: Configuration not recognized
+
+**Solution:** Verify `qase.config.json` structure:
+
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "api": { "token": "your_token" },
+ "project": "YOUR_PROJECT_CODE"
+ }
+}
+```
+
+Or use environment variables:
+
+```bash
+export QASE_MODE=testops
+export QASE_TESTOPS_API_TOKEN=your_token
+export QASE_TESTOPS_PROJECT=YOUR_PROJECT_CODE
+```
+
+---
+
+## Getting Help
+
+If you encounter issues:
+
+1. Check the [GitHub Issues](https://github.com/qase-tms/qase-javascript/issues)
+2. Review the CHANGELOG
+3. Open a new issue with:
+ - Current version (2.2.0)
+ - TestCafe version
+ - Error messages
+ - Configuration file (without sensitive data)
+ - Test code example
+ - Command used to run tests
+ - Steps to reproduce
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Multi-Project Support](MULTI_PROJECT.md)
+- CHANGELOG
+- [TestCafe Documentation](https://testcafe.io/documentation/)
diff --git a/qase-testcafe/docs/usage.md b/qase-testcafe/docs/usage.md
index 7dfe8631..5a65d006 100644
--- a/qase-testcafe/docs/usage.md
+++ b/qase-testcafe/docs/usage.md
@@ -1,163 +1,1073 @@
# Qase Integration in TestCafe
-This guide demonstrates how to integrate Qase with TestCafe, providing instructions on how to add Qase IDs, titles,
-fields, suites, comments, and file attachments to your test cases.
+This guide provides comprehensive instructions for integrating Qase with TestCafe.
+> **Configuration:** For complete configuration reference including all available options, environment variables, and examples, see the [qase-javascript-commons README](../../qase-javascript-commons/README.md).
+
+---
+
+## Table of Contents
+
+- [Adding QaseID](#adding-qaseid)
+- [Adding Title](#adding-title)
+- [Adding Fields](#adding-fields)
+- [Adding Suite](#adding-suite)
+- [Ignoring Tests](#ignoring-tests)
+- [Muting Tests](#muting-tests)
+- [Working with Attachments](#working-with-attachments)
+- [Working with Steps](#working-with-steps)
+- [Working with Parameters](#working-with-parameters)
+- [Running Tests](#running-tests)
+- [Integration Patterns](#integration-patterns)
+- [Common Use Cases](#common-use-cases)
+- [Troubleshooting](#troubleshooting)
+- [Complete Examples](#complete-examples)
+
+- [See Also](#see-also)
---
-## Adding QaseID to a Test
+## Adding QaseID
-To associate a QaseID with a test in TestCafe, use the `qase` function. This function accepts a single integer
-representing the test's ID in Qase.
+Link your TestCafe tests to existing test cases in Qase by specifying the test case ID using the `qase.id()` method with `.meta()`.
-### Example:
+### Single ID
```javascript
-import { qase } from 'testcafe-qase-reporter/qase';
+import { test } from 'testcafe';
+import { qase } from 'testcafe-reporter-qase/qase';
-const q = qase.id(1).create();
-test.meta(q)('simple test', async (t) => {
- await t.expect(true).ok();
-});
+fixture\`User Authentication\`
+ .page\`https://example.com\`;
+
+test.meta(qase.id(1).create())(
+ 'User can log in',
+ async (t) => {
+ await t.expect(true).ok();
+ }
+);
+```
+
+### Multiple IDs
+
+```javascript
+test.meta(qase.id([1, 2, 3]).create())(
+ 'Test linked to multiple cases',
+ async (t) => {
+ await t.expect(true).ok();
+ }
+);
```
---
-## Adding a Title to a Test
+## Adding Title
+
+Set a custom title for the test case (overrides auto-generated title):
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
-You can provide a title for your test using the `qase.title` function. The function accepts a string, which will be
-used as the test's title in Qase. If no title is provided, the test method name will be used by default.
+test.meta(qase.title('User can successfully log in with valid credentials').create())(
+ 'Login test',
+ async (t) => {
+ await t.expect(true).ok();
+ }
+);
+```
+
+---
-### Example:
+## Adding Fields
+
+Add metadata to your test cases using fields. Both system and custom fields are supported.
+
+### System Fields
+
+| Field | Description | Example Values |
+|-------|-------------|----------------|
+| `description` | Test case description | Any text |
+| `preconditions` | Test preconditions | Any text (supports Markdown) |
+| `postconditions` | Test postconditions | Any text |
+| `severity` | Test severity | `blocker`, `critical`, `major`, `normal`, `minor`, `trivial` |
+| `priority` | Test priority | `high`, `medium`, `low` |
+| `layer` | Test layer | `e2e`, `api`, `unit` |
+
+### Example
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test.meta(
+ qase.fields({
+ 'severity': 'critical',
+ 'priority': 'high',
+ 'layer': 'e2e',
+ 'description': 'Tests the core login functionality',
+ 'preconditions': 'User account must exist in the database',
+ }).create()
+)(
+ 'Login test with fields',
+ async (t) => {
+ await t.expect(true).ok();
+ }
+);
+```
+
+---
+
+## Adding Suite
+
+Organize tests into suites and sub-suites. TestCafe uses fixture for organization, but you can also set suite explicitly:
+
+### Using Fixture (Recommended)
```javascript
-import { qase } from 'testcafe-qase-reporter/qase';
+fixture\`Authentication Suite\`
+ .page\`https://example.com\`;
-const q = qase.title('Some title').create();
-test.meta(q)('simple test', async (t) => {
+test('Login test', async (t) => {
+ // This test belongs to "Authentication Suite"
await t.expect(true).ok();
});
```
+### Explicit Suite
+
+```javascript
+test.meta(qase.suite('Authentication').create())(
+ 'Login test',
+ async (t) => {
+ await t.expect(true).ok();
+ }
+);
+```
+
+### Nested Suites
+
+```javascript
+test.meta(qase.suite('Authentication\tLogin\tValid Credentials').create())(
+ 'Login with email and password',
+ async (t) => {
+ await t.expect(true).ok();
+ }
+);
+```
+
---
-## Adding Fields to a Test
+## Ignoring Tests
-The `qase.fields` function allows you to add additional metadata to a test case. You can specify multiple fields to
-enhance test case information in Qase.
+Exclude a test from Qase reporting. The test still executes, but results are not sent to Qase:
-### System Fields:
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
-- `description` — Description of the test case.
-- `preconditions` — Preconditions for the test case.
-- `postconditions` — Postconditions for the test case.
-- `severity` — Severity of the test case (e.g., `critical`, `major`).
-- `priority` — Priority of the test case (e.g., `high`, `low`).
-- `layer` — Test layer (e.g., `UI`, `API`).
+test.meta(qase.ignore().create())(
+ 'Ignored test',
+ async (t) => {
+ await t.expect(true).ok();
+ }
+);
+```
+
+---
-### Example:
+## Muting Tests
+
+Mark a test as muted. Muted tests are reported but do not affect the test run status:
```javascript
-import { qase } from 'testcafe-qase-reporter/qase';
+import { qase } from 'testcafe-reporter-qase/qase';
-const q = qase.fields({ 'severity': 'high', 'priority': 'medium' }).create();
-test.meta(q)('simple test', async (t) => {
- await t.expect(true).ok();
-});
+test.meta(qase.id(1).create())(
+ 'Known failing test',
+ async (t) => {
+ qase.mute();
+ await t.expect(false).ok(); // This failure won't affect the run status
+ }
+);
```
---
-## Ignoring a Test in Qase
+## Working with Attachments
-To exclude a test from being reported to Qase (while still executing the test in TestCafe), use the `qase.ignore`
-function. The test will run, but its result will not be sent to Qase.
+Attach files, screenshots, logs, and other content to your test results.
-### Example:
+### Attach File from Path
```javascript
-import { qase } from 'testcafe-qase-reporter/qase';
+import { qase } from 'testcafe-reporter-qase/qase';
-const q = qase.ignore().create();
-test.meta(q)('simple test', async (t) => {
+test('Test with file attachment', async (t) => {
+ qase.attach({ paths: '/path/to/log.txt' });
await t.expect(true).ok();
});
```
----
+### Attach Multiple Files
-## Attaching Files to a Test
+```javascript
+test('Test with multiple attachments', async (t) => {
+ qase.attach({
+ paths: [
+ '/path/to/log1.txt',
+ '/path/to/log2.txt',
+ '/path/to/screenshot.png',
+ ]
+ });
+ await t.expect(true).ok();
+});
+```
-To attach files to a test result, use the `qase.attach` function. This method supports attaching one or multiple files,
-along with optional file names, comments, and file types.
+### Attach Content from Code
-### Example:
+```javascript
+test('Test with content attachment', async (t) => {
+ qase.attach({
+ name: 'test-data.json',
+ content: JSON.stringify({ key: 'value' }),
+ type: 'application/json',
+ });
+ await t.expect(true).ok();
+});
+```
+
+### Attach TestCafe Screenshot
```javascript
-import { qase } from 'testcafe-qase-reporter/qase';
+test('Test with screenshot', async (t) => {
+ const screenshot = await t.takeScreenshot();
+
+ qase.attach({
+ name: 'page-screenshot.png',
+ content: screenshot,
+ type: 'image/png',
+ });
-test('test', async (t) => {
- qase.attach({ name: 'attachment.txt', content: 'Hello, world!', contentType: 'text/plain' });
- qase.attach({ paths: '/path/to/file' });
- qase.attach({ paths: ['/path/to/file', '/path/to/another/file'] });
await t.expect(true).ok();
});
```
+### Attach to Specific Step
+
+```javascript
+test('Test with step attachments', async (t) => {
+ await qase.step('Capture state', async (step) => {
+ const screenshot = await t.takeScreenshot();
+
+ step.attach({
+ name: 'current-state.png',
+ content: screenshot,
+ type: 'image/png',
+ });
+ });
+});
+```
+
---
-## Adding Parameters to a Test
+## Working with Steps
-You can add parameters to a test case using the `qase.parameters` function. This function accepts an object with
-parameter names and values.
+Define test steps for detailed reporting in Qase.
-### Example:
+### Basic Steps
```javascript
-import { qase } from 'testcafe-qase-reporter/qase';
+import { qase } from 'testcafe-reporter-qase/qase';
-const q = qase.parameters({ param1: 'value1', param2: 'value2' }).create();
-test.meta(q)('simple test', async (t) => {
- await t.expect(true).ok();
+test('Test with steps', async (t) => {
+ await qase.step('Navigate to login page', async () => {
+ await t.navigateTo('https://example.com/login');
+ });
+
+ await qase.step('Enter credentials', async () => {
+ await t.typeText('#email', 'user@example.com');
+ await t.typeText('#password', 'password123');
+ });
+
+ await qase.step('Click login button', async () => {
+ await t.click('#login-button');
+ });
+
+ await qase.step('Verify successful login', async () => {
+ await t.expect('#dashboard').exists;
+ });
});
```
-## Adding Group Parameters to a Test
+### Nested Steps
-To add group parameters to a test case, use the `qase.groupParameters` function. This function accepts an list with
-group parameter names.
+```javascript
+test('Test with nested steps', async (t) => {
+ await qase.step('User registration flow', async (s1) => {
+ await s1.step('Fill registration form', async (s2) => {
+ await s2.step('Enter email', async () => {
+ await t.typeText('#email', 'user@example.com');
+ });
-### Example:
+ await s2.step('Enter password', async () => {
+ await t.typeText('#password', 'password123');
+ });
+
+ await s2.step('Confirm password', async () => {
+ await t.typeText('#confirm-password', 'password123');
+ });
+ });
+
+ await s1.step('Submit form', async () => {
+ await t.click('#submit-button');
+ });
+
+ await s1.step('Verify registration success', async () => {
+ await t.expect('#success-message').exists;
+ });
+ });
+});
+```
+
+### Steps with Attachments
```javascript
-import { qase } from 'testcafe-qase-reporter/qase';
+test('Test with step attachments', async (t) => {
+ await qase.step('Capture application state', async (step) => {
+ const screenshot = await t.takeScreenshot();
-const q = qase.parameters({ param1: 'value1', param2: 'value2' }).groupParameters(['param1']).create();
-test.meta(q)('simple test', async (t) => {
- await t.expect(true).ok();
+ step.attach({
+ name: 'application-state.png',
+ content: screenshot,
+ type: 'image/png',
+ });
+
+ await t.expect(true).ok();
+ });
});
```
-## Adding Steps to a Test
+---
-You can add steps to a test case using the `qase.step` function. This function accepts a string, which will be used as
-the step description in Qase.
+## Working with Parameters
-### Example:
+Report parameterized test data to Qase.
+
+### Basic Parameters
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+test.meta(
+ qase.parameters({
+ 'browser': 'chrome',
+ 'environment': 'staging',
+ 'resolution': '1920x1080',
+ }).create()
+)(
+ 'Parameterized test',
+ async (t) => {
+ await t.expect(true).ok();
+ }
+);
+```
+
+### Group Parameters
+
+```javascript
+test.meta(
+ qase.parameters({
+ 'browser': 'chrome',
+ 'os': 'macOS',
+ 'environment': 'staging',
+ }).groupParameters(['environment']).create()
+)(
+ 'Test with grouped parameters',
+ async (t) => {
+ await t.expect(true).ok();
+ }
+);
+```
+
+### Combined with Other Metadata
```javascript
-import { qase } from 'testcafe-qase-reporter/qase';
+test.meta(
+ qase.id(1)
+ .title('Cross-browser test')
+ .fields({ 'severity': 'high' })
+ .parameters({ 'browser': 'firefox', 'os': 'Windows' })
+ .create()
+)(
+ 'Test with full metadata',
+ async (t) => {
+ await t.expect(true).ok();
+ }
+);
+```
+
+---
+
+## Running Tests
+
+### Basic Execution
+
+```bash
+# Run all tests with Qase reporter
+QASE_MODE=testops npx testcafe chrome tests/
+
+# Run specific test file
+QASE_MODE=testops npx testcafe chrome tests/login.test.js
+
+# Run with multiple reporters
+QASE_MODE=testops npx testcafe chrome tests/ -r spec,qase
+```
+
+### Multiple Browsers
+
+```bash
+# Run in multiple browsers
+QASE_MODE=testops npx testcafe chrome,firefox tests/ -r qase
+
+# Run in headless mode
+QASE_MODE=testops npx testcafe chrome:headless tests/ -r qase
+
+# Run in all installed browsers
+QASE_MODE=testops npx testcafe all tests/ -r qase
+```
+
+### With Environment Variables
+
+```bash
+# Override configuration
+QASE_MODE=testops \
+QASE_TESTOPS_PROJECT=DEMO \
+QASE_TESTOPS_API_TOKEN=your_token \
+npx testcafe chrome tests/
+```
+
+### With TestCafe Configuration
+
+Create `.testcaferc.json`:
+
+```json
+{
+ "browsers": ["chrome:headless"],
+ "src": ["tests/**/*.test.js"],
+ "reporter": ["spec", "qase"],
+ "concurrency": 3,
+ "quarantineMode": false,
+ "skipJsErrors": true,
+ "stopOnFirstFail": false
+}
+```
+
+Run with configuration:
+
+```bash
+QASE_MODE=testops npx testcafe
+```
+
+---
+
+## Troubleshooting
+
+### Reporter Not Found
+
+**Problem:** `Error: Reporter "Qase" not found` or tests run without Qase reporting.
+
+**Solutions:**
+
+1. **Verify installation:**
+ ```bash
+ npm list testcafe-reporter-Qase
+ ```
+
+2. **Reinstall if needed:**
+ ```bash
+ npm install --save-dev testcafe-reporter-Qase
+ ```
+
+3. **Check reporter name in command:**
+ ```bash
+ # Correct
+ npx testcafe chrome tests/ -r Qase
+
+ # Incorrect
+ npx testcafe chrome tests/ -r testcafe-reporter-Qase
+ ```
+
+### Tests Not Appearing in Qase
+
+**Problem:** Tests run successfully but don't appear in Qase TestOps.
+
+**Solutions:**
+
+1. **Verify mode is set:**
+ ```bash
+ echo $QASE_MODE # Should output: testops
+ ```
+
+2. **Check configuration file:**
+ ```bash
+ cat qase.config.json
+ ```
+
+3. **Enable debug logging:**
+ ```json
+ {
+ "debug": true,
+ "mode": "TestOps"
+ }
+ ```
+
+4. **Verify API token and project code:**
+ - Token has write permissions
+ - Project code matches your Qase project
+
+### Metadata Not Applied
+
+**Problem:** `qase.id()`, `qase.fields()`, or other metadata not working.
+
+**Solutions:**
+
+1. **Verify `.create()` is called:**
+ ```javascript
+ // Correct
+ test.meta(qase.id(1).create())('Test', async (t) => {});
+
+ // Incorrect (missing .create())
+ test.meta(qase.id(1))('Test', async (t) => {});
+ ```
+
+2. **Check import path:**
+ ```javascript
+ // Correct
+ import { Qase } from 'testcafe-reporter-qase/qase';
+
+ // Incorrect
+ import { Qase } from 'testcafe-reporter-Qase';
+ ```
-test('test', async (t) => {
- await qase.step('Step 1', async (s1) => {
- await s1.step('Step 1.1', async (s11) => {
- await s11.step('Step 1.1.1', async (s111) => {
- s11.attach({ name: 'attachment.txt', content: 'Hello, world!', contentType: 'text/plain' });
- await s111.expect(true).ok();
+### Steps Not Reporting
+
+**Problem:** `qase.step()` calls not appearing in Qase results.
+
+**Solutions:**
+
+1. **Ensure async/await is used:**
+ ```javascript
+ // Correct
+ await qase.step('Step name', async () => {
+ await t.click('#button');
+ });
+
+ // Incorrect (missing await)
+ qase.step('Step name', async () => {
+ await t.click('#button');
+ });
+ ```
+
+2. **Check step callback is async:**
+ ```javascript
+ // Correct
+ await qase.step('Step', async () => { ... });
+
+ // Incorrect
+ await qase.step('Step', () => { ... });
+ ```
+
+### Attachments Not Uploading
+
+**Problem:** Files or screenshots not appearing in Qase.
+
+**Solutions:**
+
+1. **Verify file path exists:**
+ ```bash
+ ls -la /path/to/file
+ ```
+
+2. **Check file permissions:**
+ ```bash
+ # File should be readable
+ chmod 644 /path/to/file
+ ```
+
+3. **Use absolute paths:**
+ ```javascript
+ const path = require('path');
+ qase.attach({
+ paths: path.resolve(__dirname, '../screenshots/test.png')
+ });
+ ```
+
+4. **Verify content type:**
+ ```javascript
+ qase.attach({
+ name: 'log.txt',
+ content: 'Log content',
+ type: 'text/plain', // Must be specified for content
+ });
+ ```
+
+### Browser Issues
+
+**Problem:** Tests fail to start or browser-related errors.
+
+**Solutions:**
+
+1. **List available browsers:**
+ ```bash
+ npx testcafe --list-browsers
+ ```
+
+2. **Use specific browser version:**
+ ```bash
+ npx testcafe "chrome:headless" tests/
+ ```
+
+3. **Check browser installation:**
+ ```bash
+ which google-chrome
+ which firefox
+ ```
+
+---
+
+## Integration Patterns
+
+### Pattern 1: Page Object Model with Qase
+
+```javascript
+// pages/LoginPage.js
+export class LoginPage {
+ constructor(t) {
+ this.t = t;
+ }
+
+ async login(email, password) {
+ await qase.step('Enter email', async () => {
+ await this.t.typeText('#email', email);
+ });
+
+ await qase.step('Enter password', async () => {
+ await this.t.typeText('#password', password);
+ });
+
+ await qase.step('Click login button', async () => {
+ await this.t.click('#login-button');
+ });
+ }
+}
+
+// tests/login.test.js
+import { test } from 'testcafe';
+import { qase } from 'testcafe-reporter-qase/qase';
+import { LoginPage } from '../pages/LoginPage';
+
+fixture\`Login Tests\`
+ .page\`https://example.com/login\`;
+
+test.meta(qase.id(1).create())(
+ 'User can login',
+ async (t) => {
+ const loginPage = new LoginPage(t);
+ await loginPage.login('user@example.com', 'password123');
+ await t.expect('#dashboard').exists;
+ }
+);
+```
+
+### Pattern 2: Data-Driven Testing
+
+```javascript
+import { test } from 'testcafe';
+import { qase } from 'testcafe-reporter-qase/qase';
+
+const users = [
+ { email: 'admin@example.com', password: 'admin123', role: 'admin' },
+ { email: 'user@example.com', password: 'user123', role: 'user' },
+ { email: 'guest@example.com', password: 'guest123', role: 'guest' },
+];
+
+fixture\`Role-based Tests\`
+ .page\`https://example.com\`;
+
+users.forEach((user) => {
+ test.meta(
+ qase.id(1)
+ .parameters({ role: user.role, email: user.email })
+ .create()
+ )(
+ \`Login as \${user.role}\`,
+ async (t) => {
+ await t.typeText('#email', user.email);
+ await t.typeText('#password', user.password);
+ await t.click('#login-button');
+ await t.expect(\`#role-\${user.role}\`).exists;
+ }
+ );
+});
+```
+
+### Pattern 3: Before/After Hooks with Reporting
+
+```javascript
+import { test } from 'testcafe';
+import { qase } from 'testcafe-reporter-qase/qase';
+
+fixture\`E2E Tests\`
+ .page\`https://example.com\`
+ .beforeEach(async (t) => {
+ await qase.step('Setup: Clear cookies', async () => {
+ await t.eval(() => {
+ document.cookie.split(';').forEach((c) => {
+ document.cookie = c.trim().split('=')[0] + '=;expires=Thu, 01 Jan 1970 00:00:00 UTC';
+ });
+ });
+ });
+ })
+ .afterEach(async (t) => {
+ await qase.step('Teardown: Capture screenshot', async (step) => {
+ const screenshot = await t.takeScreenshot();
+ step.attach({
+ name: 'final-state.png',
+ content: screenshot,
+ type: 'image/png',
});
});
- await t.expect(true).ok();
});
+
+test.meta(qase.id(1).create())('Test with hooks', async (t) => {
await t.expect(true).ok();
});
```
+
+---
+
+## Common Use Cases
+
+### Use Case 1: Basic Test with QaseID
+
+```javascript
+import { test } from 'testcafe';
+import { qase } from 'testcafe-reporter-qase/qase';
+
+fixture\`Smoke Tests\`
+ .page\`https://example.com\`;
+
+test.meta(qase.id(101).create())(
+ 'Homepage loads',
+ async (t) => {
+ await t.expect('#header').exists;
+ }
+);
+```
+
+### Use Case 2: Visual Regression with Screenshots
+
+```javascript
+test.meta(qase.id(201).create())(
+ 'Visual: Login page',
+ async (t) => {
+ const screenshot = await t.takeScreenshot();
+
+ qase.attach({
+ name: 'login-page.png',
+ content: screenshot,
+ type: 'image/png',
+ });
+
+ await t.expect('#login-form').exists;
+ }
+);
+```
+
+### Use Case 3: API Integration Test
+
+```javascript
+import { test } from 'testcafe';
+import { qase } from 'testcafe-reporter-qase/qase';
+import { RequestLogger } from 'testcafe';
+
+const apiLogger = RequestLogger(/api\.example\.com/, {
+ logRequestBody: true,
+ logResponseBody: true,
+});
+
+fixture\`API Tests\`
+ .page\`https://example.com\`
+ .requestHooks(apiLogger);
+
+test.meta(qase.id(301).create())(
+ 'API: Fetch user data',
+ async (t) => {
+ await t.click('#load-data-button');
+
+ await qase.step('Verify API call', async () => {
+ await t.expect(apiLogger.contains((r) => r.response.statusCode === 200)).ok();
+ });
+
+ qase.attach({
+ name: 'api-response.json',
+ content: JSON.stringify(apiLogger.requests[0].response.body, null, 2),
+ type: 'application/json',
+ });
+ }
+);
+```
+
+### Use Case 4: Cross-Browser Testing
+
+```javascript
+const browsers = ['chrome', 'firefox', 'safari'];
+
+browsers.forEach((browser) => {
+ test.meta(
+ qase.id(401)
+ .parameters({ browser })
+ .create()
+ )(
+ \`Test on \${browser}\`,
+ async (t) => {
+ await t.expect(true).ok();
+ }
+ );
+});
+```
+
+### Use Case 5: Mobile Viewport Testing
+
+```javascript
+import { test } from 'testcafe';
+import { qase } from 'testcafe-reporter-qase/qase';
+
+fixture\`Mobile Tests\`
+ .page\`https://example.com\`;
+
+test.meta(
+ qase.id(501)
+ .parameters({ viewport: 'mobile', width: 375, height: 667 })
+ .create()
+)(
+ 'Mobile: iPhone 8',
+ async (t) => {
+ await t.resizeWindow(375, 667);
+ await t.expect('#mobile-menu').exists;
+ }
+);
+```
+
+### Use Case 6: Form Validation Testing
+
+```javascript
+test.meta(qase.id(601).create())(
+ 'Form: Email validation',
+ async (t) => {
+ await qase.step('Enter invalid email', async () => {
+ await t.typeText('#email', 'invalid-email');
+ });
+
+ await qase.step('Submit form', async () => {
+ await t.click('#submit-button');
+ });
+
+ await qase.step('Verify error message', async () => {
+ await t.expect('#email-error').exists;
+ await t.expect('#email-error').contains('Invalid email');
+ });
+ }
+);
+```
+
+### Use Case 7: File Upload Testing
+
+```javascript
+test.meta(qase.id(701).create())(
+ 'File upload',
+ async (t) => {
+ await qase.step('Select file', async () => {
+ await t.setFilesToUpload('#file-input', ['./fixtures/test-file.pdf']);
+ });
+
+ await qase.step('Upload file', async () => {
+ await t.click('#upload-button');
+ });
+
+ await qase.step('Verify upload success', async () => {
+ await t.expect('#upload-success').exists;
+ });
+ }
+);
+```
+
+### Use Case 8: Authentication Flow
+
+```javascript
+test.meta(
+ qase.id(801)
+ .title('Complete authentication flow')
+ .fields({ severity: 'critical', priority: 'high' })
+ .create()
+)(
+ 'Auth flow',
+ async (t) => {
+ await qase.step('Navigate to login', async () => {
+ await t.navigateTo('/login');
+ });
+
+ await qase.step('Enter credentials', async () => {
+ await t.typeText('#email', 'user@example.com');
+ await t.typeText('#password', 'password123');
+ });
+
+ await qase.step('Submit login', async () => {
+ await t.click('#login-button');
+ });
+
+ await qase.step('Verify dashboard loads', async () => {
+ await t.expect('#dashboard').exists;
+ });
+
+ await qase.step('Logout', async () => {
+ await t.click('#logout-button');
+ });
+ }
+);
+```
+
+### Use Case 9: Performance Testing
+
+```javascript
+test.meta(qase.id(901).create())(
+ 'Performance: Page load time',
+ async (t) => {
+ const startTime = Date.now();
+
+ await t.navigateTo('https://example.com');
+
+ const loadTime = Date.now() - startTime;
+
+ qase.attach({
+ name: 'performance-metrics.json',
+ content: JSON.stringify({ loadTime, threshold: 3000 }, null, 2),
+ type: 'application/json',
+ });
+
+ await t.expect(loadTime).lt(3000, 'Page load time exceeds 3 seconds');
+ }
+);
+```
+
+### Use Case 10: CI/CD Integration
+
+**.github/workflows/testcafe.yml:**
+```yaml
+name: TestCafe Tests
+
+on: [push, pull_request]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ with:
+ node-version: '18'
+ - run: npm ci
+ - run: npm test
+ env:
+ QASE_MODE: testops
+ QASE_TESTOPS_PROJECT: DEMO
+ QASE_TESTOPS_API_TOKEN: \${{ secrets.QASE_TOKEN }}
+```
+
+**package.json:**
+```json
+{
+ "scripts": {
+ "test": "testcafe chrome:headless tests/ -r spec,qase"
+ }
+}
+```
+
+---
+
+## Complete Examples
+
+### Full Test Example
+
+```javascript
+import { qase } from 'testcafe-reporter-qase/qase';
+
+fixture('Complete Example')
+ .page('https://example.com');
+
+test.meta(
+ qase
+ .id(1)
+ .title('User can complete full registration flow')
+ .fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'e2e',
+ description: 'Tests complete user registration flow from start to finish',
+ preconditions: 'Application is running and database is accessible',
+ })
+ .suite('Registration\tEnd-to-End')
+ .parameters({ Browser: 'Chrome', Environment: 'staging' })
+ .create()
+)(
+ 'Comprehensive test with all features',
+ async (t) => {
+ await qase.step('Navigate to registration page', async () => {
+ await t.navigateTo('/register');
+ qase.attach({
+ name: 'page-load.txt',
+ content: 'Page loaded successfully',
+ type: 'text/plain',
+ });
+ });
+
+ await qase.step('Fill registration form', async () => {
+ await t
+ .typeText('#username', 'testuser')
+ .typeText('#email', 'test@example.com')
+ .typeText('#password', 'SecurePass123!');
+ });
+
+ await qase.step('Submit form', async () => {
+ await t.click('button[type="submit"]');
+ await t.expect(Selector('.success-message').exists).ok();
+ });
+
+ await qase.step('Verify email confirmation', async () => {
+ await t.expect(Selector('.email-sent').innerText)
+ .contains('Verification email sent');
+ });
+ }
+);
+```
+
+### Example Project Structure
+
+```
+my-project/
+├── qase.config.json
+├── tests/
+│ ├── auth.test.js
+│ ├── checkout.test.js
+│ └── ...
+├── pages/
+│ ├── LoginPage.js
+│ ├── DashboardPage.js
+│ └── ...
+└── package.json
+```
+
+---
+
+## See Also
+
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [TestCafe Documentation](https://testcafe.io/documentation)
+- [Example Tests](../../examples/single/testcafe/)
diff --git a/qase-vitest/README.md b/qase-vitest/README.md
index bb343847..fe46d2bb 100644
--- a/qase-vitest/README.md
+++ b/qase-vitest/README.md
@@ -1,39 +1,69 @@
-# Qase TestOps Vitest reporter
+# [Qase TestOps](https://qase.io) Vitest Reporter
-Qase Vitest reporter sends test results and metadata to Qase.io.
-It can work in different test automation scenarios:
+[](https://www.apache.org/licenses/LICENSE-2.0)
+[](https://www.npmjs.com/package/vitest-qase-reporter)
-* Create new test cases in Qase from existing autotests.
-* Report Vitest test results to existing test cases in Qase.
+Qase Vitest Reporter enables seamless integration between your Vitest tests and [Qase TestOps](https://qase.io), providing automatic test result reporting, test case management, and comprehensive test analytics.
-Testing frameworks that use Vitest as a test runner can also be used with Vitest reporter.
+## Features
-To install the latest version, run:
+- Link automated tests to Qase test cases by ID
+- Auto-create test cases from your test code
+- Report test results with rich metadata (fields, attachments, steps)
+- Support for parameterized tests
+- Multi-project reporting support
+- Flexible configuration (file, environment variables, Vitest config)
-```shell
-npm install vitest-qase-reporter
+## Installation
+
+```sh
+npm install --save-dev vitest-qase-reporter
+```
+
+## Quick Start
+
+**1. Create `qase.config.json` in your project root:**
+
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "project": "YOUR_PROJECT_CODE",
+ "api": {
+ "token": "YOUR_API_TOKEN"
+ }
+ }
+}
+```
+
+**2. Add Qase ID to your test:**
+
+```typescript
+import { qase } from 'vitest-qase-reporter';
+import { test, expect } from 'vitest';
+
+test(qase(1, 'Test name'), () => {
+ expect(true).toBe(true);
+});
```
-# Contents
+**3. Run your tests:**
-- [Qase TestOps Vitest reporter](#qase-testops-vitest-reporter)
-- [Contents](#contents)
- - [Getting started](#getting-started)
- - [Using Reporter](#using-reporter)
- - [Metadata](#metadata)
- - [Advanced Usage with Annotations](#advanced-usage-with-annotations)
- - [Configuration](#configuration)
- - [Documentation](#documentation)
- - [Requirements](#requirements)
+```sh
+npx vitest run
+```
+
+## Configuration
-## Getting started
+The reporter is configured via (in order of priority):
-To report your tests results to Qase, install `qase-vitest`,
-and add a reporter config in the `vitest.config.ts` file.
-A minimal configuration needs just two things:
+1. **vitest.config.ts** (Vitest-specific, highest priority)
+2. **Environment variables** (`QASE_*`)
+3. **Config file** (`qase.config.json`)
-* Qase project code, for example, in https://app.qase.io/project/DEMO the code is `DEMO`.
-* Qase API token, created on the [Apps page](https://app.qase.io/apps?app=vitest-reporter).
+### Vitest Configuration
+
+Add the reporter to your `vitest.config.ts`:
```typescript
import { defineConfig } from 'vitest/config';
@@ -59,221 +89,157 @@ export default defineConfig({
});
```
-Now, run the Vitest tests as usual.
-Test results will be reported to a new test run in Qase.
-
-```console
-$ npx vitest run
-Determining test suites to run...
-...
-qase: Project DEMO exists
-qase: Using run 42 to publish test results
-...
-
-Ran all test suites.
+### Minimal Configuration
+
+| Option | Environment Variable | Description |
+|--------|---------------------|-------------|
+| `mode` | `QASE_MODE` | Set to `testops` to enable reporting |
+| `testops.project` | `QASE_TESTOPS_PROJECT` | Your Qase project code |
+| `testops.api.token` | `QASE_TESTOPS_API_TOKEN` | Your Qase API token |
+
+### Example `qase.config.json`
+
+```json
+{
+ "mode": "testops",
+ "fallback": "report",
+ "testops": {
+ "project": "YOUR_PROJECT_CODE",
+ "api": {
+ "token": "YOUR_API_TOKEN"
+ },
+ "run": {
+ "title": "Vitest Automated Run"
+ },
+ "batch": {
+ "size": 100
+ }
+ },
+ "report": {
+ "driver": "local",
+ "connection": {
+ "local": {
+ "path": "./build/qase-report",
+ "format": "json"
+ }
+ }
+ }
+}
```
-## Using Reporter
+> **Full configuration reference:** See [qase-javascript-commons](../qase-javascript-commons/README.md) for all available options including logging, status mapping, execution plans, and more.
-The Vitest reporter has the ability to auto-generate test cases
-and suites from your test data.
+## Usage
-But if necessary, you can independently register the ID of already
-existing test cases from TMS before the executing tests. For example:
+### Link Tests with Test Cases
-### Metadata
-
-- `qase.title` - set the title of the test case
-- `qase.fields` - set the fields of the test case
-- `qase.suite` - set the suite of the test case
-- `qase.comment` - set the comment of the test case
-- `qase.parameters` - set the parameters of the test case
-- `qase.groupParameters` - set the group parameters of the test case
-- `qase.ignore` - ignore the test case in Qase. The test will be executed, but the results will not be sent to Qase.
-- `qase.step` - create a step in the test case
-- `qase.attach` - attach a file to the test case
+Associate your tests with Qase test cases using test case IDs:
+**Single ID:**
```typescript
-import { describe, it, test, expect } from 'vitest';
-import { addQaseId, withQase } from 'vitest-qase-reporter/vitest';
-
-describe('My First Test', () => {
- test(addQaseId('Several ids', [1, 2]), () => {
- expect(true).toBe(true);
- });
-
- test(addQaseId('Correct test', [3]), () => {
- expect(true).toBe(true);
- });
+import { qase } from 'vitest-qase-reporter';
+import { test, expect } from 'vitest';
- test.skip(addQaseId('Skipped test', [4]), () => {
- expect(true).toBe(true);
- });
+test(qase(1, 'Test name'), () => {
+ expect(true).toBe(true);
+});
+```
- test(addQaseId('Failed test', ['5', '6']), () => {
- expect(true).toBe(false);
- });
+**Multiple IDs:**
+```typescript
+test(qase([1, 2], 'Test covering multiple cases'), () => {
+ expect(true).toBe(true);
});
```
-### Advanced Usage with Annotations
+### Add Metadata
+
+Enhance your tests with additional information:
```typescript
-import { describe, it, expect } from 'vitest';
-import { addQaseId, withQase } from 'vitest-qase-reporter/vitest';
+import { qase } from 'vitest-qase-reporter';
+import { test, expect } from 'vitest';
-describe('Qase Annotations Example', () => {
- it(addQaseId(20, 'Basic test with qase ID'), () => {
- expect(1 + 1).toBe(2);
- });
+test('Test with metadata', async () => {
+ qase.title('Custom test title');
- it('Test with qase annotations', withQase(async ({ qase, annotate }) => {
- // Set test title
- await qase.title('Advanced Test with Annotations');
-
- // Add comment
- await qase.comment('This test demonstrates qase annotations functionality');
-
- // Set suite
- await qase.suite('Vitest Integration Suite');
-
- // Set fields
- await qase.fields({
- description: 'Test description for Qase',
- severity: 'critical',
- priority: 'high',
- layer: 'e2e'
- });
-
- // Set parameters
- await qase.parameters({
- environment: 'staging',
- browser: 'chrome',
- version: '1.0.0'
- });
-
- // Add steps
- await qase.step('Initialize test data', async () => {
- expect(true).toBe(true);
- });
-
- await qase.step('Execute main test logic', async () => {
- expect(2 + 2).toBe(4);
- });
-
- // Add attachment with content
- await qase.attach({
- name: 'test-data.json',
- content: JSON.stringify({ test: 'data' }),
- type: 'application/json'
- });
-
- // Use regular annotate for custom annotations
- await annotate('Custom annotation message', 'info');
-
- // Final assertion
- expect(Math.max(1, 2, 3)).toBe(3);
- }));
-});
-```
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'api',
+ description: 'Tests core authentication flow',
+ });
-To run tests and create a test run, execute the command (for example from folder examples):
+ qase.suite('Authentication / Login');
-```bash
-QASE_MODE=testops npx vitest run
+ expect(true).toBe(true);
+});
```
-or
+### Ignore Tests
-```bash
-npm test
-```
-
-A test run will be performed and available at:
+Exclude specific tests from Qase reporting (test still runs, but results are not sent):
-```
-https://app.qase.io/run/QASE_PROJECT_CODE
+```typescript
+test('Test not reported to Qase', () => {
+ qase.ignore();
+ expect(true).toBe(true);
+});
```
-### Multi-Project Support
+### Test Result Statuses
-Qase Vitest Reporter supports sending test results to multiple Qase projects simultaneously. You can specify different test case IDs for each project using `addQaseProjects(name, mapping)`.
+| Vitest Result | Qase Status |
+|---------------|-------------|
+| Passed | Passed |
+| Failed | Failed |
+| Skipped | Skipped |
-For detailed information, configuration, and examples, see the [Multi-Project Support Guide](docs/MULTI_PROJECT.md).
+> For more usage examples, see the [Usage Guide](docs/usage.md).
-## Configuration
-
-Reporter options (* - required):
+## Running Tests
-- `mode` - `testops`/`off` Enables reporter, default - `off`
-- `debug` - Enables debug logging, default - `false`
-- `environment` - To execute with the sending of the environment information
-- `captureLogs` - Capture console logs, default - `false`
-- `uploadAttachments` - Upload attachments to Qase, default - `false`
-- *`testops.api.token` - Token for API access, you can generate it [here](https://developers.qase.io/#authentication).
-- *`testops.project` - [Your project's code](https://help.qase.io/en/articles/9787250-how-do-i-find-my-project-code)
-- `testops.api.baseUrl` - Qase API base URL (optional)
-- `testops.run.id` - Qase test run ID, used when the test run was created earlier using CLI or API call.
-- `testops.run.title` - Set custom Run name, when new run is created
-- `testops.run.description` - Set custom Run description, when new run is created
-- `testops.run.complete` - Whether the run should be completed
+**Basic test execution:**
+```sh
+npx vitest run
+```
-Example `vitest.config.ts` config:
+**With environment variables:**
+```sh
+QASE_MODE=testops npx vitest run
+```
-```typescript
-import { defineConfig } from 'vitest/config';
+**With reporter enabled via config:**
+```sh
+npx vitest run --reporter=vitest-qase-reporter
+```
-export default defineConfig({
- test: {
- reporters: [
- 'default',
- [
- 'vitest-qase-reporter',
- {
- mode: 'testops',
- testops: {
- api: {
- token: 'api_key'
- },
- project: 'project_code',
- run: {
- complete: true,
- },
- uploadAttachments: true,
- },
- debug: true,
- captureLogs: true,
- },
- ],
- ],
- },
-});
+**Watch mode (note: reporting happens on full run completion):**
+```sh
+npx vitest
```
-You can check example configuration with multiple reporters in [example project](../examples/vitest/vitest.config.ts).
+> **Note:** Vitest is ESM-first and uses Jest-compatible API. If you're migrating from Jest, the Qase wrapper syntax is identical.
-Supported ENV variables:
+## Requirements
-- `QASE_MODE` - Same as `mode`
-- `QASE_DEBUG` - Same as `debug`
-- `QASE_ENVIRONMENT` - Same as `environment`
-- `QASE_TESTOPS_API_TOKEN` - Same as `testops.api.token`
-- `QASE_TESTOPS_PROJECT` - Same as `testops.project`
-- `QASE_TESTOPS_RUN_ID` - Pass Run ID from ENV and override reporter option `testops.run.id`
-- `QASE_TESTOPS_RUN_TITLE` - Same as `testops.run.title`
-- `QASE_TESTOPS_RUN_DESCRIPTION` - Same as `testops.run.description`
+- Node.js >= 14
+- Vitest >= 3.0.0
## Documentation
-For detailed documentation and advanced usage, see [USAGE.md](./docs/usage.md).
-
-## Requirements
+| Guide | Description |
+|-------|-------------|
+| [Usage Guide](docs/usage.md) | Complete usage reference with all methods and options |
+| [Attachments](docs/ATTACHMENTS.md) | Adding screenshots, logs, and files to test results |
+| [Steps](docs/STEPS.md) | Defining test steps for detailed reporting |
+| [Multi-Project Support](docs/MULTI_PROJECT.md) | Reporting to multiple Qase projects |
+| [Upgrade Guide](docs/UPGRADE.md) | Migration guide for breaking changes |
-We maintain the reporter on LTS versions of Node. You can find the current versions by following
-the [link](https://nodejs.org/en/about/releases/)
+## Examples
-`vitest >= 3.0.0`
+See the [examples directory](../examples/) for complete working examples.
-
+## License
-[auth]: https://developers.qase.io/#authentication
+Apache License 2.0. See [LICENSE](LICENSE) for details.
diff --git a/qase-vitest/docs/ATTACHMENTS.md b/qase-vitest/docs/ATTACHMENTS.md
new file mode 100644
index 00000000..9187fc05
--- /dev/null
+++ b/qase-vitest/docs/ATTACHMENTS.md
@@ -0,0 +1,310 @@
+# Attachments in Vitest
+
+This guide covers how to attach files, screenshots, logs, and other content to your Qase test results.
+
+---
+
+## Overview
+
+Qase Vitest Reporter supports attaching various types of content to test results:
+
+- **Files** — Attach files from the filesystem
+- **Screenshots** — Attach images captured during test execution
+- **Logs** — Attach text logs or console output
+- **Binary data** — Attach any binary content from memory
+
+Attachments can be added to:
+- **Test cases** — Visible in the overall test result
+- **Test steps** — Visible in specific step results
+
+---
+
+## Attaching Files
+
+### From File Path
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test('Test with file attachment', async () => {
+ await qase.attach({ paths: './test/fixtures/test-file.txt' });
+
+ expect(true).toBe(true);
+});
+```
+
+### Multiple Files
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test('Test with multiple attachments', async () => {
+ await qase.attach({
+ paths: [
+ './test/fixtures/file1.txt',
+ './test/fixtures/file2.log',
+ './test/fixtures/screenshot.png'
+ ]
+ });
+
+ expect(true).toBe(true);
+});
+```
+
+---
+
+## Attaching Content from Memory
+
+### Text Content
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test('Test with text attachment', async () => {
+ await qase.attach({
+ name: 'log.txt',
+ content: 'Test execution log content',
+ type: 'text/plain',
+ });
+
+ expect(true).toBe(true);
+});
+```
+
+### Binary Content (Screenshots)
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+import fs from 'fs';
+
+test('Test with binary attachment', async () => {
+ const screenshot = fs.readFileSync('./test/fixtures/screenshot.png');
+
+ await qase.attach({
+ name: 'screenshot.png',
+ content: screenshot,
+ type: 'image/png',
+ });
+
+ expect(true).toBe(true);
+});
+```
+
+### JSON Data
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test('Test with JSON attachment', async () => {
+ const data = {
+ userId: 123,
+ status: 'active',
+ timestamp: new Date().toISOString(),
+ };
+
+ await qase.attach({
+ name: 'test-data.json',
+ content: JSON.stringify(data, null, 2),
+ type: 'application/json',
+ });
+
+ expect(true).toBe(true);
+});
+```
+
+---
+
+## Attaching to Steps
+
+Attach content to a specific test step:
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test('Test with step attachments', async () => {
+ await qase.step('Initialize test data', async () => {
+ const testData = { user: 'testuser', role: 'admin' };
+
+ await qase.attach({
+ name: 'init-data.json',
+ content: JSON.stringify(testData, null, 2),
+ type: 'application/json',
+ });
+ });
+
+ await qase.step('Execute test', async () => {
+ await qase.attach({
+ name: 'execution-log.txt',
+ content: 'Test execution completed successfully',
+ type: 'text/plain',
+ });
+
+ expect(true).toBe(true);
+ });
+});
+```
+
+---
+
+## Method Reference
+
+### `qase.attach()`
+
+Attach content to the test case.
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `paths` | `string` or `string[]` | No* | Path(s) to file(s) to attach |
+| `content` | `string` or `Buffer` | No* | Content to attach |
+| `name` | `string` | No | Custom filename (auto-detected from path) |
+| `type` | `string` | No | MIME type (auto-detected from extension) |
+
+\* Either `paths` or `content` must be provided, but not both.
+
+**CommonJS:**
+```javascript
+const { qase } = require('vitest-qase-reporter/vitest');
+
+await qase.attach({ paths: './path/to/file.txt' });
+```
+
+**ES Modules:**
+```javascript
+import { qase } from 'vitest-qase-reporter/vitest';
+
+await qase.attach({ paths: './path/to/file.txt' });
+```
+
+---
+
+## MIME Types
+
+Common MIME types are auto-detected based on file extension:
+
+| Extension | MIME Type |
+|-----------|-----------|
+| `.png` | `image/png` |
+| `.jpg`, `.jpeg` | `image/jpeg` |
+| `.gif` | `image/gif` |
+| `.svg` | `image/svg+xml` |
+| `.txt` | `text/plain` |
+| `.log` | `text/plain` |
+| `.json` | `application/json` |
+| `.xml` | `application/xml` |
+| `.html` | `text/html` |
+| `.csv` | `text/csv` |
+| `.pdf` | `application/pdf` |
+| `.zip` | `application/zip` |
+
+For other file types, specify `type` explicitly.
+
+---
+
+## Common Use Cases
+
+### File Attachments
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+import fs from 'fs';
+
+test('Test with generated file attachment', async () => {
+ const reportData = 'Test Report\n============\nStatus: Passed\n';
+ const filePath = './test/output/report.txt';
+
+ fs.writeFileSync(filePath, reportData);
+
+ await qase.attach({ paths: filePath });
+
+ expect(fs.existsSync(filePath)).toBe(true);
+
+ // Cleanup
+ fs.unlinkSync(filePath);
+});
+```
+
+### JSON Data Logging
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test('Test with structured data logging', async () => {
+ const testResults = {
+ passed: 10,
+ failed: 2,
+ skipped: 1,
+ duration: '5.2s',
+ };
+
+ await qase.attach({
+ name: 'test-results.json',
+ content: JSON.stringify(testResults, null, 2),
+ type: 'application/json',
+ });
+
+ expect(testResults.passed).toBeGreaterThan(0);
+});
+```
+
+### Binary Content
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test('Test with binary data attachment', async () => {
+ const imageData = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', 'base64');
+
+ await qase.attach({
+ name: 'test-image.png',
+ content: imageData,
+ type: 'image/png',
+ });
+
+ expect(imageData).toBeDefined();
+});
+```
+
+---
+
+## Troubleshooting
+
+### Attachments Not Appearing
+
+1. Verify the file path exists and is readable
+2. Check file permissions
+3. Enable debug logging to see upload status:
+ ```json
+ {
+ "debug": true
+ }
+ ```
+
+### Large Files
+
+Large attachments may slow down test execution. Consider:
+- Compressing logs before attaching
+- Using selective logging (e.g., only on failure)
+- Setting reasonable size limits
+
+### Binary Data Issues
+
+When attaching binary data, always specify:
+- `name` with appropriate extension
+- `type` if extension doesn't match content type
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Steps Guide](STEPS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-vitest/docs/MULTI_PROJECT.md b/qase-vitest/docs/MULTI_PROJECT.md
index 6603d09c..539f2862 100644
--- a/qase-vitest/docs/MULTI_PROJECT.md
+++ b/qase-vitest/docs/MULTI_PROJECT.md
@@ -6,6 +6,8 @@ Qase Vitest Reporter supports sending test results to multiple Qase projects sim
* Different projects track the same functionality with different test case IDs
* You want to maintain separate test runs for different environments or teams
+---
+
## Configuration
For detailed configuration options, refer to the [qase-javascript-commons README](../../qase-javascript-commons/README.md#multi-project-support).
@@ -14,40 +16,151 @@ For detailed configuration options, refer to the [qase-javascript-commons README
Set `mode` to `testops_multi` in your Vitest config (e.g. in `vitest.config.ts` or `qase.config.json`) and add the `testops_multi` section with `default_project` and `projects`.
+**Example configuration:**
+
+```json
+{
+ "mode": "testops_multi",
+ "testops_multi": {
+ "default_project": "PROJ1",
+ "projects": [
+ {
+ "code": "PROJ1",
+ "api": {
+ "token": "your_api_token_for_proj1"
+ }
+ },
+ {
+ "code": "PROJ2",
+ "api": {
+ "token": "your_api_token_for_proj2"
+ }
+ }
+ ]
+ }
+}
+```
+
+---
+
## Using `addQaseProjects(name, mapping)`
Use the `addQaseProjects(name, mapping)` helper from `vitest-qase-reporter/vitest` to build a test name that includes multi-project markers. The reporter parses the title and sets `testops_project_mapping`:
```typescript
+import { test } from 'vitest';
import { addQaseId, addQaseProjects } from 'vitest-qase-reporter/vitest';
// Single project (legacy)
-it(addQaseId('login flow', [100]), async () => { ... });
+test(addQaseId('login flow', [100]), async () => {
+ expect(true).toBe(true);
+});
// Multi-project
-it(addQaseProjects('login flow', { PROJ1: [100], PROJ2: [200] }), async () => { ... });
+test(addQaseProjects('login flow', { PROJ1: [100], PROJ2: [200] }), async () => {
+ expect(true).toBe(true);
+});
// Multiple IDs per project
-it(addQaseProjects('checkout', { PROJ1: [10, 11], PROJ2: [20] }), async () => { ... });
+test(addQaseProjects('checkout', { PROJ1: [10, 11], PROJ2: [20] }), async () => {
+ expect(true).toBe(true);
+});
```
+**Key points:**
+
+- Single project with single ID: `test(addQaseId('test name', [100]), () => { ... })`
+- Multi-project: `test(addQaseProjects('test name', { PROJ1: [100], PROJ2: [200] }), () => { ... })`
+- Multiple IDs per project: `test(addQaseProjects('test name', { PROJ1: [10, 11], PROJ2: [20] }), () => { ... })`
+
Project codes must match `testops_multi.projects[].code` in your config.
+---
+
## Tests Without Project Mapping
Tests whose name does not contain multi-project markers (and have no single-project `(Qase ID: …)`) are sent to the `default_project`. Results without any case ID are sent to the default project without linking to a test case.
+---
+
## Important Notes
-1. **Project codes must match**: Keys in `addQaseProjects('name', { PROJ1: [1], ... })` must match `testops_multi.projects[].code`.
+1. **Project codes must match**: Keys in `addQaseProjects('name', { PROJ1: [1], ... })` must match `testops_multi.projects[].code` in config.
2. **Mode**: Use `mode: 'testops_multi'` in Vitest reporter config.
3. **Title format**: The helper produces a title like `Name (Qase PROJ1: 1,2) (Qase PROJ2: 3)`.
+4. **API tokens**: Each project in `testops_multi.projects[]` can have its own API token for separate authentication.
+
+---
## Examples
See the [multi-project Vitest example](../../examples/multiProject/vitest/) for a complete runnable setup.
+### Complete Example
+
+Here's a complete Vitest test file showing multi-project usage:
+
+```typescript
+import { test, expect, describe } from 'vitest';
+import { addQaseId, addQaseProjects } from 'vitest-qase-reporter/vitest';
+
+describe('Multi-project test suite', () => {
+ // Test reported to two projects
+ test(addQaseProjects('User can login successfully', { PROJ1: [1], PROJ2: [2] }), async () => {
+ const username = 'testuser';
+ const password = 'password123';
+
+ expect(username).toBeDefined();
+ expect(password).toBeDefined();
+ });
+
+ // Test with multiple case IDs per project
+ test(addQaseProjects('Checkout process works', { PROJ1: [10, 11], PROJ2: [20] }), async () => {
+ const cart = { items: 2, total: 99.99 };
+
+ expect(cart.items).toBeGreaterThan(0);
+ expect(cart.total).toBeGreaterThan(0);
+ });
+
+ // Single-project test (uses default_project)
+ test(addQaseId('Test reported to default project', [50]), async () => {
+ expect(1 + 1).toBe(2);
+ });
+
+ // Test without Qase metadata (goes to default_project without case ID)
+ test('Regular test without Qase tracking', async () => {
+ expect(true).toBe(true);
+ });
+});
+```
+
+---
+
## Troubleshooting
-* Ensure `mode` is `testops_multi` and project codes match the config.
-* Use `addQaseProjects(name, mapping)` (or an equivalent title format) so the reporter can parse the mapping from the test name.
+### Results Not Appearing in All Projects
+
+* Ensure `mode` is `testops_multi` (not `TestOps`) and project codes match the config
+* Check that project codes in `addQaseProjects()` match config codes exactly (case-sensitive)
+* Ensure each project has a valid API token with write permissions
+* Use `addQaseProjects(name, mapping)` (or an equivalent title format) so the reporter can parse the mapping from the test name
+
+### Wrong Test Cases Linked
+
+* Verify the mapping object has correct project codes as keys
+* Check that test case IDs exist in the respective projects
+* Enable debug logging to see how the reporter parses multi-project markers
+
+### Default Project Not Working
+
+* Ensure `default_project` is set in `testops_multi` config
+* Verify the default project code matches one of the projects in the `projects` array
+* Tests without multi-project markers will only report to the default project
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Examples](../../examples/multiProject/vitest/)
diff --git a/qase-vitest/docs/STEPS.md b/qase-vitest/docs/STEPS.md
new file mode 100644
index 00000000..c5b20977
--- /dev/null
+++ b/qase-vitest/docs/STEPS.md
@@ -0,0 +1,391 @@
+# Test Steps in Vitest
+
+This guide covers how to define and report test steps for detailed execution tracking in Qase.
+
+---
+
+## Overview
+
+Test steps provide granular visibility into test execution. Each step is reported separately, showing:
+
+- Step name and description
+- Step status (passed/failed)
+- Step duration
+- Attachments (if any)
+- Error details (on failure)
+
+---
+
+## Defining Steps
+
+### Using Async Function
+
+Define steps as async functions with callbacks:
+
+```typescript
+import { describe, test, expect } from 'vitest';
+import { withQase } from 'vitest-qase-reporter/vitest';
+
+describe('Test suite', () => {
+ test('Test with multiple steps', withQase(async ({ qase }) => {
+ await qase.step('Initialize the environment', async () => {
+ // Set up test environment
+ });
+
+ await qase.step('Test Core Functionality of the app', async () => {
+ // Exercise core functionality
+ });
+
+ await qase.step('Verify Expected Behavior of the app', async () => {
+ // Assert expected behavior
+ expect(true).toBe(true);
+ });
+ }));
+});
+```
+
+### Step Parameters
+
+Steps can include parameters for dynamic naming:
+
+```typescript
+import { describe, test, expect } from 'vitest';
+import { withQase } from 'vitest-qase-reporter/vitest';
+
+describe('Test suite', () => {
+ test('Test with dynamic step names', withQase(async ({ qase }) => {
+ const username = 'john@example.com';
+
+ await qase.step(`Login as user ${username}`, async () => {
+ // Login logic
+ });
+
+ await qase.step(`Verify ${username} profile loaded`, async () => {
+ expect(username).toBe('john@example.com');
+ });
+ }));
+});
+```
+
+---
+
+## Nested Steps
+
+Create hierarchical step structures:
+
+```typescript
+import { describe, test, expect } from 'vitest';
+import { withQase } from 'vitest-qase-reporter/vitest';
+
+describe('Test suite', () => {
+ test('Test with nested steps', withQase(async ({ qase }) => {
+ await qase.step('Complete user registration', async () => {
+ await qase.step('Fill registration form', async () => {
+ // Fill form fields
+ });
+
+ await qase.step('Submit registration', async () => {
+ // Click submit button
+ });
+ });
+
+ await qase.step('Verify registration success', async () => {
+ expect(true).toBe(true);
+ });
+ }));
+});
+```
+
+---
+
+## Steps with Expected Result and Data
+
+Define expected results and data for steps:
+
+```typescript
+import { describe, test, expect } from 'vitest';
+import { withQase } from 'vitest-qase-reporter/vitest';
+
+describe('Test suite', () => {
+ test('Test with expected results', withQase(async ({ qase }) => {
+ await qase.step(
+ 'Click button',
+ async () => {
+ // Click action
+ },
+ 'Button should be clicked',
+ 'Button data'
+ );
+
+ await qase.step(
+ 'Fill form',
+ async () => {
+ // Form filling action
+ },
+ 'Form should be filled',
+ 'Form input data'
+ );
+
+ await qase.step(
+ 'Submit form',
+ async () => {
+ // Submit action
+ },
+ 'Form should be submitted',
+ 'Form submission data'
+ );
+ }));
+});
+```
+
+**Signature:**
+```typescript
+await qase.step(
+ name: string,
+ callback: () => Promise | void,
+ expectedResult?: string,
+ data?: string
+): Promise
+```
+
+---
+
+## Steps with Attachments
+
+Attach content to a specific step:
+
+```typescript
+import { describe, test, expect } from 'vitest';
+import { withQase } from 'vitest-qase-reporter/vitest';
+
+describe('Test suite', () => {
+ test('Test with step attachments', withQase(async ({ qase }) => {
+ await qase.step('Capture application state', async () => {
+ const state = JSON.stringify({ user: 'john', status: 'active' });
+
+ qase.attach({
+ name: 'app-state.json',
+ content: state,
+ type: 'application/json',
+ });
+ });
+
+ await qase.step('Verify state', async () => {
+ expect(true).toBe(true);
+ });
+ }));
+});
+```
+
+---
+
+## Step Status
+
+Steps automatically inherit status from execution:
+
+| Execution | Step Status |
+|-----------|-------------|
+| Completes normally | Passed |
+| Throws Error/AssertionError | Failed |
+| Other exception | Invalid |
+
+---
+
+## Best Practices
+
+### Keep Steps Atomic
+
+Each step should represent a single action:
+
+```typescript
+// Good: One action per step
+await qase.step('Navigate to login page', async () => {
+ // Navigation logic
+});
+
+await qase.step('Enter username', async () => {
+ // Fill username
+});
+
+// Avoid: Multiple actions in one step
+await qase.step('Fill form and submit', async () => { // Too broad
+ // Multiple actions
+});
+```
+
+### Use Descriptive Names
+
+```typescript
+// Good: Clear action description
+await qase.step('Verify user is redirected to dashboard', async () => {
+ expect(window.location.pathname).toBe('/dashboard');
+});
+
+// Avoid: Vague names
+await qase.step('Check page', async () => {
+ expect(window.location.pathname).toBe('/dashboard');
+});
+```
+
+### Include Context in Step Names
+
+```typescript
+// Good: Include relevant context
+await qase.step(`Add product '${productName}' to cart`, async () => {
+ await addToCart(productName);
+});
+
+// Better than generic:
+await qase.step('Add product', async () => {
+ await addToCart(productName);
+});
+```
+
+---
+
+## Common Patterns
+
+### Page Object Steps
+
+```typescript
+import { describe, test, expect } from 'vitest';
+import { withQase } from 'vitest-qase-reporter/vitest';
+
+class LoginPage {
+ async login(qase, username: string, password: string) {
+ await qase.step(`Enter username: ${username}`, async () => {
+ // Fill username field
+ });
+
+ await qase.step('Enter password', async () => {
+ // Fill password field (don't log password)
+ });
+
+ await qase.step('Click login button', async () => {
+ // Click submit
+ });
+ }
+}
+
+describe('Authentication', () => {
+ test('User can login', withQase(async ({ qase }) => {
+ const loginPage = new LoginPage();
+ await loginPage.login(qase, 'user@example.com', 'password');
+ }));
+});
+```
+
+### API Testing Steps
+
+```typescript
+import { describe, test, expect } from 'vitest';
+import { withQase } from 'vitest-qase-reporter/vitest';
+
+describe('API tests', () => {
+ test('API returns correct user data', withQase(async ({ qase }) => {
+ let response;
+
+ await qase.step('Send GET request to /api/users/1', async () => {
+ response = await fetch('https://api.example.com/users/1');
+ });
+
+ await qase.step('Verify response status is 200', async () => {
+ expect(response.status).toBe(200);
+ });
+
+ await qase.step('Verify response contains user data', async () => {
+ const data = await response.json();
+ expect(data.id).toBe(1);
+ expect(data.name).toBeDefined();
+ });
+ }));
+});
+```
+
+### Setup/Teardown Steps
+
+```typescript
+import { describe, test, expect, beforeEach, afterEach } from 'vitest';
+import { withQase } from 'vitest-qase-reporter/vitest';
+
+describe('User tests', () => {
+ beforeEach(async (context) => {
+ // Note: Steps in hooks may require special handling
+ });
+
+ afterEach(async (context) => {
+ // Cleanup logic
+ });
+
+ test('Test user operations', withQase(async ({ qase }) => {
+ await qase.step('Setup: Create test user', async () => {
+ // Create user in database
+ });
+
+ await qase.step('Perform user action', async () => {
+ // Test logic
+ expect(true).toBe(true);
+ });
+
+ await qase.step('Cleanup: Delete test user', async () => {
+ // Remove user from database
+ });
+ }));
+});
+```
+
+---
+
+## Troubleshooting
+
+### Steps Not Appearing
+
+1. Verify you're using `withQase` wrapper for tests with steps
+2. Check that steps are executed within a test context with `Qase` parameter
+3. Enable debug logging to trace step recording
+4. **Ensure you're using `await` with async step callbacks** - Missing `await` is the most common issue
+
+```typescript
+// Incorrect: Missing await
+qase.step('Step name', async () => { // Step won't be recorded properly
+ // Logic
+});
+
+// Correct: Using await
+await qase.step('Step name', async () => {
+ // Logic
+});
+```
+
+### Nested Steps Flattened
+
+Ensure you're using the async callbacks correctly for nesting:
+
+```typescript
+// Correct: Nested callbacks
+await qase.step('Parent step', async () => {
+ await qase.step('Child step', async () => {
+ // Child step logic
+ });
+});
+
+// Incorrect: Sequential, not nested
+await qase.step('Step 1', async () => {
+ // Step 1 logic
+});
+await qase.step('Step 2', async () => { // Not nested under Step 1
+ // Step 2 logic
+});
+```
+
+### Step Duration Shows 0
+
+Steps need measurable execution time. Very fast steps may show 0ms duration.
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Attachments Guide](ATTACHMENTS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-vitest/docs/UPGRADE.md b/qase-vitest/docs/UPGRADE.md
new file mode 100644
index 00000000..0e87e8b8
--- /dev/null
+++ b/qase-vitest/docs/UPGRADE.md
@@ -0,0 +1,346 @@
+# Upgrade Guide: Vitest Reporter
+
+This guide covers migration steps between major versions of the Qase Vitest Reporter.
+
+---
+
+## Version History
+
+| Version | Release Date | Node.js Support | Key Changes |
+|---------|--------------|-----------------|-------------|
+| 1.1.0 | January 2026 | >= 14 | Current stable release with enhanced metadata support |
+| 1.0.0 | August 2025 | >= 14 | Initial release with unified qase-javascript-commons |
+
+---
+
+## Upgrading to 1.x
+
+### Breaking Changes
+
+The vitest-qase-reporter is currently in its first major version series (1.x). No migration from a previous major version is required.
+
+### Current Version Features
+
+Version 1.1.0 includes:
+
+- Full support for Qase TestOps API with batch result upload
+- Jest-compatible API for easy migration from Jest
+- Test case linking via wrapper function pattern: `test(qase(id, 'name'), callback)`
+- Rich metadata support: titles, fields, suites, parameters, comments
+- Async test steps with `qase.step()`
+- File and content-based attachments with `qase.attach()`
+- Multi-project support for reporting to multiple Qase projects
+- Flexible configuration via `qase.config.json` or `vitest.config.ts`
+
+---
+
+## Configuration
+
+### Current Format (v1.x)
+
+Configuration uses the modern qase-javascript-commons format:
+
+```typescript
+// vitest.config.ts
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ test: {
+ reporters: [
+ 'default', // Keep default Vitest reporter
+ [
+ 'vitest-qase-reporter',
+ {
+ mode: 'testops',
+ debug: false,
+ testops: {
+ api: {
+ token: process.env.QASE_API_TOKEN,
+ },
+ project: 'DEMO',
+ run: {
+ title: 'Vitest Automated Run',
+ description: 'Test run from CI/CD pipeline',
+ complete: true,
+ },
+ batch: {
+ size: 100,
+ },
+ },
+ },
+ ],
+ ],
+ },
+});
+```
+
+**Alternative: qase.config.json**
+
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "api": {
+ "token": "api_token_here"
+ },
+ "project": "DEMO",
+ "run": {
+ "complete": true
+ }
+ }
+}
+```
+
+**Command Line:**
+
+```bash
+QASE_MODE=testops npx vitest run
+```
+
+---
+
+## Import Pattern
+
+### Current Import (v1.x)
+
+```typescript
+import { qase } from 'vitest-qase-reporter';
+```
+
+**CommonJS:**
+
+```javascript
+const { qase } = require('vitest-qase-reporter');
+```
+
+**Note:** Vitest uses the root package export (no subpath required), similar to Jest's API.
+
+---
+
+## API Reference
+
+### Test Case ID Linking
+
+The Vitest reporter uses Jest-compatible syntax:
+
+```typescript
+import { qase } from 'vitest-qase-reporter';
+import { describe, test, expect } from 'vitest';
+
+describe('User Authentication', () => {
+ // Single test case ID
+ test(qase(1, 'User can login'), () => {
+ expect(true).toBe(true);
+ });
+
+ // Multiple test case IDs
+ test(qase([1, 2], 'Multiple IDs'), () => {
+ expect(true).toBe(true);
+ });
+});
+```
+
+### Metadata Methods
+
+```typescript
+test('Test with metadata', () => {
+ qase.title('Custom test title');
+ qase.fields({ severity: 'critical', priority: 'high', layer: 'unit' });
+ qase.suite('Authentication / Login');
+ qase.comment('This test covers the main login flow');
+ qase.parameters({ environment: 'staging', mode: 'development' });
+
+ expect(true).toBe(true);
+});
+```
+
+### Steps (Async)
+
+```typescript
+test('Test with steps', async () => {
+ await qase.step('Initialize environment', async () => {
+ // Setup code
+ });
+
+ await qase.step('Perform action', async () => {
+ // Test logic
+ });
+
+ await qase.step('Verify result', async () => {
+ // Assertions
+ expect(result).toBe(expected);
+ });
+});
+```
+
+**With expected results and data:**
+
+```typescript
+await qase.step(
+ 'Verify login success',
+ async () => {
+ expect(user.authenticated).toBe(true);
+ },
+ 'User should be authenticated', // Expected result (optional)
+ 'User data' // Data (optional)
+);
+```
+
+### Nested Steps
+
+```typescript
+test('Test with nested steps', async () => {
+ await qase.step('Parent step', async () => {
+ await qase.step('Child step 1', async () => {
+ // Nested logic
+ });
+
+ await qase.step('Child step 2', async () => {
+ // More nested logic
+ });
+ });
+});
+```
+
+### Attachments
+
+```typescript
+// Path-based attachment
+qase.attach({ paths: '/path/to/log.txt' });
+
+// Multiple files
+qase.attach({ paths: ['/path/to/file1.txt', '/path/to/file2.log'] });
+
+// Content-based attachment
+qase.attach({
+ name: 'test-data.json',
+ content: JSON.stringify({ key: 'value' }),
+ contentType: 'application/json',
+});
+```
+
+---
+
+## Migrating from Jest
+
+If you're migrating from Jest to Vitest, the Qase reporter API is fully compatible:
+
+**Jest code:**
+
+```javascript
+const { qase } = require('jest-qase-reporter/jest');
+
+test(qase(1, 'Test'), async () => {
+ await qase.step('Step 1', async () => { /* ... */ });
+ qase.attach({ paths: 'file.txt' });
+});
+```
+
+**Vitest code (same API):**
+
+```typescript
+import { qase } from 'vitest-qase-reporter';
+
+test(qase(1, 'Test'), async () => {
+ await qase.step('Step 1', async () => { /* ... */ });
+ qase.attach({ paths: 'file.txt' });
+});
+```
+
+The only difference is the import statement - everything else remains identical.
+
+---
+
+## Compatibility Notes
+
+### Node.js Version Support
+
+- **Current (1.1.0):** Node.js >= 14
+
+### Vitest Version Support
+
+- **Current (1.1.0):** Vitest >= 3.0.0
+- Tested with Vitest 3.x
+
+### Framework Compatibility
+
+- ES Modules recommended (Vitest default)
+- CommonJS supported
+- TypeScript support with full type definitions
+- Works with Vitest workspace feature for monorepos
+- Compatible with Vitest UI and browser mode
+
+---
+
+## Troubleshooting
+
+### Common Issues
+
+#### Issue: Module not found after installation
+
+**Solution:** Ensure you're importing from the correct package:
+
+```typescript
+// Correct
+import { qase } from 'vitest-qase-reporter';
+
+// Incorrect - no subpath needed
+import { qase } from 'vitest-qase-reporter/vitest';
+```
+
+#### Issue: Reporter not running
+
+**Solution:** Verify that `QASE_MODE=testops` is set when running tests:
+
+```bash
+QASE_MODE=testops npx vitest run
+```
+
+Or configure `mode: 'testops'` in your `vitest.config.ts` or `qase.config.json`.
+
+#### Issue: Tests not reported to Qase
+
+**Solution:** Check that:
+1. API token is set: `process.env.QASE_API_TOKEN`
+2. Project code is correct: `project: 'YOUR_PROJECT_CODE'`
+3. Mode is set to TestOps: `mode: 'TestOps'`
+4. Reporter is in the `reporters` array in vitest.config.ts
+
+#### Issue: Configuration format errors
+
+**Solution:** Ensure the configuration follows the correct structure:
+
+```typescript
+{
+ mode: 'testops',
+ testops: {
+ api: { token: 'your_token' },
+ project: 'YOUR_PROJECT_CODE',
+ }
+}
+```
+
+---
+
+## Getting Help
+
+If you encounter issues:
+
+1. Check the [GitHub Issues](https://github.com/qase-tms/qase-javascript/issues)
+2. Review the CHANGELOG
+3. Open a new issue with:
+ - Current version (1.1.0)
+ - Vitest version
+ - Error messages
+ - Configuration file (without sensitive data)
+ - Steps to reproduce
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Multi-Project Support](MULTI_PROJECT.md)
+- CHANGELOG
+- [Vitest Documentation](https://vitest.dev/)
diff --git a/qase-vitest/docs/usage.md b/qase-vitest/docs/usage.md
index 705396a1..3b7e7255 100644
--- a/qase-vitest/docs/usage.md
+++ b/qase-vitest/docs/usage.md
@@ -1,286 +1,836 @@
-# Qase Syntax
+# Qase Integration in Vitest
-> [**Click here**](../../examples/vitest) to view Example tests for the following syntax.
+This guide provides comprehensive instructions for integrating Qase with Vitest.
-Here is the complete list of syntax options available for the reporter:
-- [Qase Id](#qase-id)
-- [Qase Title](#qase-title)
-- [Steps](#steps)
-- [Fields](#fields)
-- [Suite](#suite)
-- [Parameters](#parameters)
-- [Comment](#comment)
-- [Attach](#attach)
-- [Ignore](#ignore)
+> **Configuration:** For complete configuration reference including all available options, environment variables, and examples, see the [qase-javascript-commons README](../../qase-javascript-commons/README.md).
-If you do not use any Qase syntax, the reporter uses the title from the `describe` and `it`/`test` functions as the Suite and Test case title respectively, when publishing results.
+---
+
+## Table of Contents
+
+- [Adding QaseID](#adding-qaseid)
+- [Adding Title](#adding-title)
+- [Adding Fields](#adding-fields)
+- [Adding Suite](#adding-suite)
+- [Ignoring Tests](#ignoring-tests)
+- [Muting Tests](#muting-tests)
+- [Working with Attachments](#working-with-attachments)
+- [Working with Steps](#working-with-steps)
+- [Working with Parameters](#working-with-parameters)
+- [Multi-Project Support](#multi-project-support)
+- [Running Tests](#running-tests)
+- [Complete Examples](#complete-examples)
+- [Troubleshooting](#troubleshooting)
+- [Integration Patterns](#integration-patterns)
+- [Common Use Cases](#common-use-cases)
+
+- [See Also](#see-also)
+---
+
+## Adding QaseID
+
+Link your automated tests to existing test cases in Qase by specifying the test case ID.
+
+### Single ID
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test(qase(1, 'Test name'), () => {
+ expect(true).toBe(true);
+});
+```
+
+### Multiple IDs
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test(qase([1, 2, 3], 'Test covering multiple cases'), () => {
+ expect(true).toBe(true);
+});
+```
+
+### Multi-Project Support
-
+To send test results to multiple Qase projects simultaneously, see the [Multi-Project Support Guide](MULTI_PROJECT.md).
-### Import Statement
---
-Add the following statement at the beginning of your spec file, before any tests.
+
+## Adding Title
+
+Set a custom title for the test case (overrides auto-generated title):
```typescript
-import { qase, withQase } from 'vitest-qase-reporter/vitest';
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test('Test with custom title', () => {
+ qase.title('User can successfully complete checkout');
+ expect(true).toBe(true);
+});
```
-
-### Qase ID
---
-You can link one or more Qase Ids to a test.
+## Adding Fields
+
+Add metadata to your test cases using fields. Both system and custom fields are supported.
+
+### System Fields
+
+| Field | Description | Example Values |
+|-------|-------------|----------------|
+| `description` | Test case description | Any text |
+| `preconditions` | Test preconditions | Any text (supports Markdown) |
+| `postconditions` | Test postconditions | Any text |
+| `severity` | Test severity | `blocker`, `critical`, `major`, `normal`, `minor`, `trivial` |
+| `priority` | Test priority | `high`, `medium`, `low` |
+| `layer` | Test layer | `e2e`, `api`, `unit` |
+
+### Example
```typescript
-it(qase(1, "A test with Qase Id"), () => {
- // test logic
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test('Test with fields', () => {
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'api',
+ description: 'Tests core authentication flow',
+ preconditions: 'User database must be seeded with test data',
+ postconditions: 'Test user is deleted after test completion',
+ });
+
+ expect(true).toBe(true);
});
+```
+
+---
+
+## Adding Suite
+
+Organize tests into suites and sub-suites:
+
+### Simple Suite
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test('Test in a suite', () => {
+ qase.suite('Authentication');
+ expect(true).toBe(true);
+});
+```
-it(qase(['2', '3'], "A test with multiple Qase Ids"), () => {
- // test logic
+### Nested Suites
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test('Test in nested suites', () => {
+ qase.suite('Application\tAuthentication\tLogin\tEdge Cases');
+ expect(true).toBe(true);
});
```
-
+---
-### Qase Title
----
+## Ignoring Tests
-The `qase.title()` method is used to set the title of a test case, both when creating a new test case from the result, and when updating the title of an existing test case - *if used with `qase.id()`.*
+Exclude a test from Qase reporting. The test still executes, but results are not sent to Qase:
```typescript
-it("This won't appear in Qase", withQase(async ({ qase }) => {
- await qase.title("This text will be the title of the test, in Qase");
- // Test logic here
-}));
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test('Test not reported to Qase', () => {
+ qase.ignore();
+ expect(true).toBe(true);
+});
```
-If you don't explicitly set a title using this method, the title specified in the `it(..)` function will be used for creating new test cases. However, if this method is defined, it always takes precedence and overrides the title from the `it(..)` function.
+---
-
+## Muting Tests
-### Steps
----
+Mark a test as muted. Muted tests are reported but do not affect the test run status:
-The reporter uses the title from the `qase.step` function as the step title. By providing clear and descriptive step names, you make it easier to understand the test's flow when reviewing the test case.
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
-Additionally, these steps get their own result in the Qase Test run, offering a well-organized summary of the test flow. This helps quickly identify the cause of any failures.
+test('Muted test', () => {
+ qase.mute();
+ expect(true).toBe(true);
+});
+```
-You can also provide an expected result and input data for each step, which will be displayed in Qase.
+---
+
+## Working with Attachments
+
+Attach files, screenshots, logs, and other content to your test results.
+
+### Attach File from Path
```typescript
-it('A Test case with steps, updated from code', withQase(async ({ qase }) => {
- await qase.step('Initialize the environment', async () => {
- // Set up test environment
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test('Test with file attachment', () => {
+ qase.attach({ paths: './test-data/screenshot.png' });
+ expect(true).toBe(true);
+});
+```
+
+### Attach Multiple Files
+
+```typescript
+test('Test with multiple attachments', () => {
+ qase.attach({
+ paths: [
+ './test-data/log.txt',
+ './test-data/config.json',
+ './test-data/screenshot.png',
+ ],
});
- await qase.step('Test Core Functionality of the app', async () => {
- // Exercise core functionality
+ expect(true).toBe(true);
+});
+```
+
+### Attach Content from Code
+
+```typescript
+test('Test with inline content', () => {
+ qase.attach({
+ name: 'test-log.txt',
+ content: 'Test execution log content',
+ type: 'text/plain',
});
- await qase.step('Verify Expected Behavior of the app', async () => {
- // Assert expected behavior
+ qase.attach({
+ name: 'data.json',
+ content: JSON.stringify({ test: 'data', status: 'passed' }),
+ type: 'application/json',
});
-}));
+
+ expect(true).toBe(true);
+});
```
-#### Steps with Expected Result and Data
+### Attach to Specific Step
```typescript
-it('A Test case with steps including expected results and data', withQase(async ({ qase }) => {
- await qase.step('Click button', async () => {
- // Click action
- }, 'Button should be clicked', 'Button data');
-
- await qase.step('Fill form', async () => {
- // Form filling action
- }, 'Form should be filled', 'Form input data');
-}));
+test('Test with step-specific attachment', async () => {
+ await qase.step('Capture screenshot', async () => {
+ const screenshot = Buffer.from('image-data', 'base64');
+ qase.attach({
+ name: 'screenshot.png',
+ content: screenshot,
+ type: 'image/png',
+ });
+ });
+
+ expect(true).toBe(true);
+});
```
-
-### Fields
+### Supported MIME Types
+
+Common MIME types are auto-detected. You can also specify explicitly:
+
+| Extension | MIME Type |
+|-----------|-----------|
+| `.png` | `image/png` |
+| `.jpg`, `.jpeg` | `image/jpeg` |
+| `.gif` | `image/gif` |
+| `.txt` | `text/plain` |
+| `.json` | `application/json` |
+| `.xml` | `application/xml` |
+| `.html` | `text/html` |
+| `.pdf` | `application/pdf` |
+
+> For more details, see [Attachments Guide](ATTACHMENTS.md).
+
---
-You can define the `description`, `pre-conditions`, `post-conditions`, and fields such as `severity`, `priority`, and `layer` using this method, which enables you to specify and maintain the context of the case directly within your code.
+## Working with Steps
+
+Define test steps for detailed reporting in Qase.
+
+### Using Async Function
```typescript
-it('Maintain your test meta-data from code', withQase(async ({ qase }) => {
- await qase.fields({
- severity: 'high',
- priority: 'medium',
- layer: 'api',
- precondition: 'add your precondition',
- postcondition: 'add your postcondition',
- description: `Code it quick, fix it slow,
- Tech debt grows where shortcuts go,
- Refactor later? Ha! We know.`
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test('Test with steps', async () => {
+ await qase.step('Initialize the environment', async () => {
+ // Setup code
});
- // test logic here
-}));
+
+ await qase.step('Test Core Functionality', async () => {
+ // Test logic
+ });
+
+ await qase.step('Verify Expected Behavior', async () => {
+ // Assertions
+ });
+});
```
-
+### Nested Steps
+```typescript
+test('Test with nested steps', async () => {
+ await qase.step('Parent step', async () => {
+ await qase.step('Child step 1', async () => {
+ // Nested step logic
+ });
-### Suite
----
+ await qase.step('Child step 2', async () => {
+ // Another nested step
+ });
+ });
+});
+```
-You can use this method to nest the resulting test cases in a particular suite. There's something to note here – suites, unlike test cases, are not identified uniquely by the Reporter. Therefore, when defining an existing suite - the title of the suite is used for matching.
+### Steps with Expected Result
```typescript
-it("Test with a defined suite", withQase(async ({ qase }) => {
- await qase.suite("Suite defined with qase.suite()");
- /*
- * Or, nest multiple levels of suites.
- * `\t` is used for dividing each suite name.
- */
-}));
-
-it("Test with a nested suite", withQase(async ({ qase }) => {
- await qase.suite("Application\tAuthentication\tLogin\tEdge_case");
- // test logic here
-}));
+test('Test with step metadata', async () => {
+ await qase.step(
+ 'Click login button',
+ async () => {
+ // Click action
+ },
+ 'Button should be clicked', // Expected result
+ 'Button data' // Data
+ );
+});
```
-
-### Parameters
+> For more details, see [Steps Guide](STEPS.md).
+
---
-Parameters are a great way to make your tests more dynamic, reusable, and data-driven. By defining parameters in this method, you can ensure only one test case with all the parameters is created in your Qase project, avoiding duplication.
+## Working with Parameters
+
+Report parameterized test data to Qase.
+
+### Basic Parameterized Test
```typescript
-const testCases = [
- { browser: "Chromium", username: "@alice", password: "123" },
- { browser: "Firefox", username: "@bob", password: "456" },
- { browser: "Webkit", username: "@charlie", password: "789" },
-];
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect, describe } from 'vitest';
-testCases.forEach(({ browser, username, password }) => {
- it(`Test login with ${browser}`, withQase(async ({ qase }) => {
- await qase.title("Verify if page loads on all browsers");
+const browsers = ['Chrome', 'Firefox', 'Safari'];
- await qase.parameters({ Browser: browser }); // Single parameter
- // test logic
- }));
+describe('Browser compatibility tests', () => {
+ test.each(browsers)('Test on %s', (browser) => {
+ qase.title(`Test on ${browser}`);
+ qase.parameters({ browser });
+ expect(true).toBe(true);
+ });
});
+```
+
+### Group Parameters
-testCases.forEach(({ username, password }) => {
- it(`Test login with ${username} using qase.groupParameters`, withQase(async ({ qase }) => {
- await qase.title("Verify if user is able to login with their username.");
+```typescript
+const testData = [
+ { username: 'user1', password: 'pass1' },
+ { username: 'user2', password: 'pass2' },
+];
- await qase.groupParameters({ // Group parameters
+describe('Login tests', () => {
+ test.each(testData)('Login with $username', ({ username, password }) => {
+ qase.title('User login test');
+ qase.groupParameters({
Username: username,
Password: password,
});
- // test logic
- }));
+ expect(true).toBe(true);
+ });
+});
+```
+
+---
+
+## Multi-Project Support
+
+Send test results to multiple Qase projects simultaneously with different test case IDs for each project.
+
+For detailed configuration, examples, and troubleshooting, see the [Multi-Project Support Guide](MULTI_PROJECT.md).
+
+---
+
+## Running Tests
+
+### Basic Execution
+
+```sh
+npx vitest run
+```
+
+### With Environment Variables
+
+```sh
+QASE_MODE=testops QASE_TESTOPS_PROJECT=DEMO npx vitest run
+```
+
+### With Test Plan
+
+```sh
+QASE_TESTOPS_PLAN_ID=123 npx vitest run
+```
+
+### With Existing Test Run
+
+```sh
+QASE_TESTOPS_RUN_ID=456 npx vitest run
+```
+
+---
+
+## Complete Examples
+
+### Full Test Example
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect, describe } from 'vitest';
+
+describe('User Authentication', () => {
+ test(qase(1, 'User can login with valid credentials'), async () => {
+ qase.title('Successful user login');
+
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'e2e',
+ description: 'Verifies that a user can log in with valid credentials',
+ });
+
+ qase.suite('Authentication\tLogin');
+
+ await qase.step('Navigate to login page', async () => {
+ // Navigation logic
+ });
+
+ await qase.step('Enter credentials', async () => {
+ // Form filling logic
+ });
+
+ await qase.step('Submit form', async () => {
+ // Submit logic
+ });
+
+ await qase.step('Verify successful login', async () => {
+ qase.attach({
+ name: 'login-success.png',
+ content: Buffer.from('screenshot-data'),
+ type: 'image/png',
+ });
+ expect(true).toBe(true);
+ });
+ });
});
```
-
-### Comment
+### Example Project Structure
+
+```
+my-project/
+├── qase.config.json
+├── vitest.config.ts
+├── tests/
+│ ├── auth.test.ts
+│ └── ...
+└── package.json
+```
+
---
-In addition to `qase.step()`, this method can be used to provide any additional context to your test, it helps maintain the code by clarifying the expected result of the test.
+
+## Troubleshooting
+
+### Tests Not Appearing in Qase
+
+1. Verify `mode` is set to `TestOps` (not `off` or `report`)
+2. Check API token has write permissions
+3. Verify project code is correct
+4. Check for errors in console output (enable `debug: true`)
+
+### ESM Module Resolution Errors
+
+**Problem:** `Cannot find module 'vitest-qase-reporter/vitest'`
+
+**Solution:**
+1. Verify package is installed: `npm list vitest-qase-reporter`
+2. Check `package.json` has `"type": "module"` for ESM projects
+3. Use correct import syntax: `import { qase } from 'vitest-qase-reporter/vitest'`
+
+### vitest.config.ts vs vite.config.ts
+
+**Problem:** Reporter not loaded when using `vite.config.ts`
+
+**Solution:** Always use `vitest.config.ts` for Vitest-specific configuration. While Vitest can use `vite.config.ts`, test reporters should be configured in `vitest.config.ts`:
```typescript
-it("A test case with qase.comment()", withQase(async ({ qase }) => {
- /*
- * Please note, this comment is added to a Result, not to the Test case.
- */
- await qase.comment("This comment is added to the result");
- // test logic here
-}));
+// vitest.config.ts (preferred)
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ test: {
+ reporters: ['default', 'vitest-qase-reporter/vitest'],
+ },
+});
```
-
-### Attach
+### Watch Mode vs Run Mode Reporting
+
+**Problem:** Results not reported in watch mode
+
+**Solution:** Qase reporter sends results after full test suite completion. In watch mode (`npx vitest`), results are sent only when you exit or trigger a full re-run. Use `npx vitest run` for CI/CD reporting.
+
+### Attachments Not Uploading
+
+**Problem:** Attachments missing in Qase
+
+**Solution:**
+1. Verify file path exists and is readable
+2. Enable `uploadAttachments: true` in config
+3. Check file size (large files may take time)
+4. Enable debug logging to see upload status
+
+### Results Going to Wrong Test Cases
+
+**Problem:** Test results mapped to incorrect test case IDs
+
+**Solution:**
+1. Verify QaseID matches the test case ID in Qase
+2. Check for duplicate IDs in your test suite
+3. Verify you're using the correct project code
+
+### TypeScript Import Errors
+
+**Problem:** `Module '"vitest-qase-reporter"' has no exported member 'qase'`
+
+**Solution:**
+1. Ensure TypeScript version compatibility (>= 4.5)
+2. Check `tsconfig.json` has `"moduleResolution": "node"` or `"bundler"`
+3. Try restarting TypeScript server in your IDE
+
---
-This method can help attach one, or more files to the test's result. You can also add the file's contents directly from code. For example:
+
+## Integration Patterns
+
+### Vitest Workspace Support
+
+For monorepo setups with Vitest workspaces:
```typescript
-it('Test result with attachment', withQase(async ({ qase }) => {
-
- // To attach a single file
- await qase.attach({
- paths: "./attachments/test-file.txt",
+// vitest.workspace.ts
+import { defineWorkspace } from 'vitest/config';
+
+export default defineWorkspace([
+ {
+ test: {
+ name: 'unit',
+ include: ['src/**/*.test.ts'],
+ reporters: ['default', 'vitest-qase-reporter/vitest'],
+ },
+ },
+ {
+ test: {
+ name: 'integration',
+ include: ['tests/**/*.test.ts'],
+ reporters: ['default', 'vitest-qase-reporter/vitest'],
+ },
+ },
+]);
+```
+
+### In-Source Testing with Qase
+
+Vitest supports in-source testing. You can use Qase with this pattern:
+
+```typescript
+// src/math.ts
+export function add(a: number, b: number) {
+ return a + b;
+}
+
+if (import.meta.vitest) {
+ const { test, expect } = import.meta.vitest;
+ const { qase } = await import('vitest-qase-reporter/vitest');
+
+ test(qase(1, 'Addition works'), () => {
+ expect(add(1, 2)).toBe(3);
});
+}
+```
- // Add multiple attachments.
- await qase.attach({ paths: ['/path/to/file', '/path/to/another/file'] });
+### Concurrent Tests with Qase
- // Upload file's contents directly from code.
- await qase.attach({
- name: "attachment.txt",
- content: "Hello, world!",
- type: "text/plain",
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect, describe } from 'vitest';
+
+describe.concurrent('Parallel test suite', () => {
+ test(qase(1, 'Test 1'), async () => {
+ await qase.step('Step 1', async () => {
+ expect(true).toBe(true);
+ });
});
- // test logic here
-}));
+
+ test(qase(2, 'Test 2'), async () => {
+ await qase.step('Step 2', async () => {
+ expect(true).toBe(true);
+ });
+ });
+});
```
-
-### Ignore
----
-If this method is added, the reporter will exclude the test's result from the report sent to Qase. While the test will still be executed by vitest, its result will not be considered by the reporter.
+### Snapshot Testing with Qase
```typescript
-it("This test is executed by vitest; however, it is NOT reported to Qase", withQase(async ({ qase }) => {
- await qase.ignore();
-// test logic here
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test(qase(1, 'Component snapshot'), () => {
+ qase.fields({
+ layer: 'unit',
+ description: 'Verifies component renders correctly',
+ });
+
+ const component = { name: 'Button', props: { label: 'Click me' } };
+ expect(component).toMatchSnapshot();
+});
+```
+
+### Using vi.mock with Qase Reporting
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect, vi } from 'vitest';
+
+vi.mock('./api', () => ({
+ fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: 'Test User' })),
}));
+
+test(qase(1, 'Test with mocked API'), async () => {
+ qase.fields({
+ layer: 'unit',
+ description: 'Tests component with mocked API calls',
+ });
+
+ const { fetchUser } = await import('./api');
+ const user = await fetchUser();
+ expect(user.name).toBe('Test User');
+});
```
-### Advanced Usage with Annotations
---
-For more complex scenarios, you can combine multiple annotations in a single test:
+## Common Use Cases
+
+### Use Case 1: Report with Workspace Projects
```typescript
-it('Advanced test with multiple annotations', withQase(async ({ qase, annotate }) => {
- // Set test title
- await qase.title('Advanced Test with Multiple Annotations');
-
- // Add comment
- await qase.comment('This test demonstrates advanced qase annotations functionality');
-
- // Set suite
- await qase.suite('Vitest Integration Suite');
-
- // Set fields
- await qase.fields({
- description: 'Test description for Qase',
- severity: 'critical',
- priority: 'high',
- layer: 'e2e'
+// vitest.workspace.ts
+import { defineWorkspace } from 'vitest/config';
+
+export default defineWorkspace([
+ {
+ test: {
+ name: 'backend',
+ include: ['packages/backend/**/*.test.ts'],
+ reporters: [
+ 'default',
+ [
+ 'vitest-qase-reporter/vitest',
+ {
+ testops: { project: 'BACKEND' },
+ },
+ ],
+ ],
+ },
+ },
+ {
+ test: {
+ name: 'frontend',
+ include: ['packages/frontend/**/*.test.ts'],
+ reporters: [
+ 'default',
+ [
+ 'vitest-qase-reporter/vitest',
+ {
+ testops: { project: 'FRONTEND' },
+ },
+ ],
+ ],
+ },
+ },
+]);
+```
+
+### Use Case 2: Run in CI with Coverage
+
+```yaml
+# .github/workflows/test.yml
+name: Test
+
+on: [push, pull_request]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 18
+ - run: npm ci
+ - run: npx vitest run --coverage
+ env:
+ QASE_MODE: testops
+ QASE_TESTOPS_API_TOKEN: ${{ secrets.QASE_API_TOKEN }}
+ QASE_TESTOPS_PROJECT: DEMO
+```
+
+### Use Case 3: Use Concurrent Tests for Performance
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect, describe } from 'vitest';
+
+describe.concurrent('API performance tests', () => {
+ const endpoints = ['/users', '/posts', '/comments'];
+
+ endpoints.forEach((endpoint, index) => {
+ test(qase(index + 1, `Test ${endpoint} endpoint`), async () => {
+ qase.parameters({ endpoint });
+ qase.fields({ layer: 'api', priority: 'high' });
+
+ await qase.step(`Call ${endpoint}`, async () => {
+ // API call logic
+ expect(true).toBe(true);
+ });
+ });
});
-
- // Set parameters
- await qase.parameters({
- environment: 'staging',
- browser: 'chrome',
- version: '1.0.0'
+});
+```
+
+### Use Case 4: Migrate from Jest Reporter
+
+Vitest uses Jest-compatible API, so migration is straightforward:
+
+**Before (Jest):**
+```typescript
+const { qase } = require('jest-qase-reporter/jest');
+
+test(qase(1, 'Test name'), () => {
+ expect(true).toBe(true);
+});
+```
+
+**After (Vitest):**
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test(qase(1, 'Test name'), () => {
+ expect(true).toBe(true);
+});
+```
+
+### Use Case 5: Dynamic Test Generation with Parameters
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect, describe } from 'vitest';
+
+const testMatrix = [
+ { browser: 'Chrome', os: 'Windows', viewport: '1920x1080' },
+ { browser: 'Firefox', os: 'macOS', viewport: '1920x1080' },
+ { browser: 'Safari', os: 'macOS', viewport: '1440x900' },
+];
+
+describe('Cross-browser compatibility', () => {
+ testMatrix.forEach(({ browser, os, viewport }, index) => {
+ test(qase(index + 1, `Test on ${browser}`), () => {
+ qase.title(`Compatibility test: ${browser} on ${os}`);
+ qase.groupParameters({ Browser: browser, OS: os, Viewport: viewport });
+ qase.fields({
+ severity: 'normal',
+ priority: 'medium',
+ layer: 'e2e',
+ });
+
+ expect(true).toBe(true);
+ });
});
-
- // Add steps
- await qase.step('Initialize test data', async () => {
- // Setup logic
+});
+```
+
+### Use Case 6: Test with Rich Metadata and Attachments
+
+```typescript
+import { qase } from 'vitest-qase-reporter/vitest';
+import { test, expect } from 'vitest';
+
+test(qase(1, 'E2E checkout flow'), async () => {
+ qase.title('User completes checkout successfully');
+
+ qase.fields({
+ severity: 'blocker',
+ priority: 'high',
+ layer: 'e2e',
+ description: 'Tests the complete checkout flow from cart to confirmation',
+ preconditions: '- User logged in\n- Cart has items\n- Payment method configured',
+ postconditions: 'Order created in database',
});
-
- await qase.step('Execute main test logic', async () => {
- // Main test logic
+
+ qase.suite('E2E\tCheckout\tHappy Path');
+
+ await qase.step('Add items to cart', async () => {
+ qase.attach({
+ name: 'cart-items.json',
+ content: JSON.stringify({ items: ['item1', 'item2'] }),
+ type: 'application/json',
+ });
});
-
- // Add attachment with content
- await qase.attach({
- name: 'test-data.json',
- content: JSON.stringify({ test: 'data' }),
- type: 'application/json'
+
+ await qase.step('Proceed to checkout', async () => {
+ // Checkout logic
});
-
- // Use regular annotate for custom annotations
- await annotate('Custom annotation message', 'info');
-
- // Test assertions
+
+ await qase.step('Complete payment', async () => {
+ qase.attach({
+ name: 'payment-confirmation.png',
+ content: Buffer.from('screenshot-data'),
+ type: 'image/png',
+ });
+ });
+
expect(true).toBe(true);
-}));
+});
```
+
+---
+
+## See Also
+
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Attachments Guide](ATTACHMENTS.md)
+- [Steps Guide](STEPS.md)
+- [Multi-Project Support](MULTI_PROJECT.md)
+- [Upgrade Guide](UPGRADE.md)
diff --git a/qase-wdio/README.md b/qase-wdio/README.md
index 76c01474..3e88199c 100644
--- a/qase-wdio/README.md
+++ b/qase-wdio/README.md
@@ -1,190 +1,374 @@
-# Qase TMS WebDriverIO reporter
+# [Qase TestOps](https://qase.io) WebdriverIO Reporter
-Publish results simple and easy.
+[](https://www.apache.org/licenses/LICENSE-2.0)
+[](https://www.npmjs.com/package/wdio-qase-reporter)
-To install the latest version, run:
+Qase WebdriverIO Reporter enables seamless integration between your WebdriverIO (WDIO) tests and [Qase TestOps](https://qase.io), providing automatic test result reporting, test case management, and comprehensive test analytics.
-```sh
-npm install -D wdio-qase-reporter
-```
+## Features
-## Getting started
+- Link automated tests to Qase test cases by ID
+- Auto-create test cases from your test code
+- Report test results with rich metadata (fields, attachments, steps)
+- Support for parameterized tests
+- Multi-project reporting support
+- Flexible configuration (file, environment variables, wdio.conf.js)
+- Support for both Mocha/Jasmine and Cucumber test frameworks
-The WebDriverIO reporter can auto-generate test cases
-and suites from your test data.
-Test results of subsequent test runs will match the same test cases
-as long as their names and file paths don't change.
+## Installation
-You can also annotate the tests with the IDs of existing test cases
-from Qase.io before executing tests. It's a more reliable way to bind
-autotests to test cases, that persists when you rename, move, or
-parameterize your tests.
+```bash
+npm install --save-dev wdio-qase-reporter
+```
-For more information, see the [Usage Guide](docs/usage.md).
+## Quick Start
-For example:
+**1. Create `qase.config.json` in your project root:**
-### Mocha/Jasmine
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "project": "YOUR_PROJECT_CODE",
+ "api": {
+ "token": "YOUR_API_TOKEN"
+ }
+ }
+}
+```
-```typescript
-import {qase} from "wdio-qase-reporter";
+**2. Configure reporter in `wdio.conf.js`:**
-describe('My First Test', () => {
- it(qase(1, 'Several ids'), () => {
- expect(true).to.equal(true);
- });
+```javascript
+const WDIOQaseReporter = require('wdio-qase-reporter').default;
+const { afterRunHook, beforeRunHook } = require('wdio-qase-reporter');
- // a test can check multiple test cases
- it(qase([2,3], 'Correct test'), () => {
- expect(true).to.equal(true);
- });
+exports.config = {
+ reporters: [[WDIOQaseReporter, {
+ disableWebdriverStepsReporting: true,
+ disableWebdriverScreenshotsReporting: true,
+ useCucumber: false,
+ }]],
- it('With steps',async () => {
- await qase.step('Step 1', async (s1) => {
- await s1.step('Step 1.1', async () => {
- // do something
- s1.attach({name: 'screenshot.png', type: 'image/png', content: await browser.takeScreenshot()})
- })
+ // Hooks
+ onPrepare: async function() {
+ await beforeRunHook();
+ },
+ onComplete: async function() {
+ await afterRunHook();
+ },
- qase.attach({name: 'log.txt', content: 'test', type: 'text/plain'})
+ // ... other options
+};
+```
+
+**3. Add Qase ID to your test:**
- await s1.step('Step 1.2', async () => {
- // do something
- })
- })
+**For Mocha/Jasmine:**
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Authentication', () => {
+ it(qase(1, 'User can log in'), () => {
expect(true).to.equal(true);
});
});
```
-### Cucumber
-
+**For Cucumber:**
```gherkin
-Feature: Test user role
+Feature: User Authentication
- @QaseId=3
- Scenario: Login
- Given I test login
+ @QaseId=1
+ Scenario: User can log in
+ Given I am on the login page
+ When I enter valid credentials
+ Then I should see the dashboard
```
-To execute WebDriverIO tests and report them to Qase.io, run the command:
+**4. Run your tests:**
```bash
-QASE_MODE=testops wdio run ./wdio.conf.ts
-```
-
-
-
-
-
-A test run will be performed and available at:
-
+QASE_MODE=testops npx wdio run wdio.conf.js
```
-https://app.qase.io/run/QASE_PROJECT_CODE
-```
-
-### Multi-Project Support
-
-Qase WebdriverIO Reporter supports sending test results to multiple Qase projects simultaneously. You can specify different test case IDs for each project using `qase.projects(mapping, name)`.
-
-For detailed information, configuration, and examples, see the [Multi-Project Support Guide](docs/MULTI_PROJECT.md).
## Configuration
-Qase WebDriverIO reporter can be configured in multiple ways:
+The reporter is configured via (in order of priority):
+
+1. **`wdio.conf.js` reporter options** (WDIO-specific, highest priority)
+2. **Environment variables** (`QASE_*`)
+3. **Config file** (`qase.config.json`)
-- using a separate config file `qase.config.json`,
-- using environment variables (they override the values from the configuration files).
+### Minimal Configuration
-For a full list of configuration options, see
-the [Configuration reference](../qase-javascript-commons/README.md#configuration).
+| Option | Environment Variable | Description |
+|--------|---------------------|-------------|
+| `mode` | `QASE_MODE` | Set to `testops` to enable reporting |
+| `testops.project` | `QASE_TESTOPS_PROJECT` | Your Qase project code |
+| `testops.api.token` | `QASE_TESTOPS_API_TOKEN` | Your Qase API token |
-Example `qase.config.json` config:
+### Example `qase.config.json`
```json
{
"mode": "testops",
- "debug": true,
+ "fallback": "report",
"testops": {
+ "project": "YOUR_PROJECT_CODE",
"api": {
- "token": "api_key"
+ "token": "YOUR_API_TOKEN"
},
- "project": "project_code",
"run": {
- "complete": true
+ "title": "WDIO Automated Run"
+ },
+ "batch": {
+ "size": 100
+ }
+ },
+ "report": {
+ "driver": "local",
+ "connection": {
+ "local": {
+ "path": "./build/qase-report",
+ "format": "json"
+ }
}
}
}
```
-Also, you need to configure the reporter using the `wdio.conf.ts` or `wdio.conf.js` files:
+### WDIO Reporter Options
-```ts
-// wdio.conf.ts
-import WDIOQaseReporter from 'wdio-qase-reporter';
-import type { Options } from '@wdio/types';
-import { afterRunHook, beforeRunHook } from 'wdio-qase-reporter';
+Configure in `wdio.conf.js`:
-export const config: Options.Testrunner = {
- reporters: [[WDIOQaseReporter, {
- disableWebdriverStepsReporting: true,
- disableWebdriverScreenshotsReporting: true,
- useCucumber: false,
- }]],
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `disableWebdriverStepsReporting` | boolean | `false` | Disable automatic step reporting for WebDriver commands |
+| `disableWebdriverScreenshotsReporting` | boolean | `false` | Disable automatic screenshot attachments |
+| `useCucumber` | boolean | `false` | Enable Cucumber integration (set to `true` if using Cucumber) |
- // ...
- // =====
- // Hooks
- // =====
- onPrepare: async function() {
- await beforeRunHook();
- },
- onComplete: async function() {
- await afterRunHook();
- },
- // ... other options
-};
+> **Full configuration reference:** See [qase-javascript-commons](../qase-javascript-commons/README.md) for all available options including logging, status mapping, execution plans, and more.
+
+## Usage
+
+### Link Tests with Test Cases
+
+**Mocha/Jasmine - Single ID:**
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it(qase(1, 'Test with single ID'), () => {
+ expect(true).to.equal(true);
+ });
+});
```
+**Mocha/Jasmine - Multiple IDs:**
```javascript
-// wdio.conf.js
-const WDIOQaseReporter = require('wdio-qase-reporter').default;
-const {beforeRunHook, afterRunHook} = require('wdio-qase-reporter');
+it(qase([1, 2, 3], 'Test linked to multiple cases'), () => {
+ expect(true).to.equal(true);
+});
+```
+**Cucumber - Tags:**
+```gherkin
+@QaseId=1
+Scenario: Single test case
-exports.config = {
- reporters: [[WDIOQaseReporter, {
- disableWebdriverStepsReporting: true,
- disableWebdriverScreenshotsReporting: true,
- useCucumber: false,
- }]],
+@QaseId=2,3,4
+Scenario: Multiple test cases
+```
- // ...
- // =====
- // Hooks
- // =====
- onPrepare: async function() {
- await beforeRunHook();
- },
- onComplete: async function() {
- await afterRunHook();
- },
- // ... other options
-};
+### Add Metadata
+
+**Mocha/Jasmine:**
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+it('Test with metadata', () => {
+ qase.title('Custom test title');
+ qase.fields({
+ 'severity': 'critical',
+ 'priority': 'high',
+ 'layer': 'api',
+ });
+ qase.suite('Authentication\tLogin');
+
+ expect(true).to.equal(true);
+});
+```
+
+**Cucumber:**
+```gherkin
+@QaseId=1
+@Title=Custom Test Title
+@Suite=Authentication
+Scenario: Login with metadata
+ Given I am on the login page
+```
+
+### Add Steps
+
+**Mocha/Jasmine:**
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+it('Test with steps', async () => {
+ await qase.step('Navigate to login page', async (step) => {
+ await browser.url('/login');
+ });
+
+ await qase.step('Enter credentials', async (step) => {
+ await step.step('Type email', async () => {
+ await $('#email').setValue('user@example.com');
+ });
+
+ await step.step('Type password', async () => {
+ await $('#password').setValue('password123');
+ });
+ });
+
+ await qase.step('Submit form', async () => {
+ await $('#login-button').click();
+ });
+});
+```
+
+**Cucumber:** Cucumber steps are automatically reported as Qase steps.
+
+### Attach Files
+
+**Mocha/Jasmine:**
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+it('Test with attachments', async () => {
+ const screenshot = await browser.takeScreenshot();
+
+ qase.attach({
+ name: 'screenshot.png',
+ content: screenshot,
+ type: 'image/png',
+ });
+
+ qase.attach({ paths: '/path/to/log.txt' });
+});
+```
+
+**Cucumber:** Use step attachments or configure automatic screenshot capture.
+
+### Ignore Tests
+
+**Mocha/Jasmine:**
+```javascript
+it('Ignored test', () => {
+ qase.ignore();
+ expect(true).to.equal(true);
+});
```
-Additional options of the reporter in the `wdio.conf.ts` or `wdio.conf.js` files:
+**Cucumber:** No direct support - use tags to filter tests.
-- `disableWebdriverStepsReporting` - optional parameter(`false` by default), in order to log only custom steps to the reporter.
-- `disableWebdriverScreenshotsReporting` - optional parameter(`false` by default), in order to not attach screenshots to the reporter.
-- `useCucumber` - optional parameter (`false` by default), if you use Cucumber, set it to true
+### Test Result Statuses
+
+| WDIO Result | Qase Status |
+|-------------|-------------|
+| passed | passed |
+| failed | failed |
+| skipped | skipped |
+
+> For more usage examples, see the [Usage Guide](docs/usage.md).
+
+## Running Tests
+
+### Basic Execution
+
+```bash
+# Run all tests
+QASE_MODE=testops npx wdio run wdio.conf.js
+
+# Run specific spec file
+QASE_MODE=testops npx wdio run wdio.conf.js --spec ./tests/login.spec.js
+
+# Run specific suite
+QASE_MODE=testops npx wdio run wdio.conf.js --suite smoke
+```
+
+### Cucumber
+
+```bash
+# Run Cucumber tests
+QASE_MODE=testops npx wdio run wdio.conf.js
+```
+
+### Environment Variables
+
+```bash
+# Override configuration
+QASE_MODE=testops \
+QASE_TESTOPS_PROJECT=DEMO \
+QASE_TESTOPS_API_TOKEN=your_token \
+npx wdio run wdio.conf.js
+```
+
+### Parallel Execution
+
+```bash
+# Run with max instances
+QASE_MODE=testops npx wdio run wdio.conf.js --maxInstances 5
+```
+
+## Multi-Project Support
+
+Qase WebdriverIO Reporter supports sending test results to multiple Qase projects simultaneously using `qase.projects()`:
+
+**Mocha/Jasmine:**
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+it(qase.projects({ PROJ1: [1], PROJ2: [2] }, 'Multi-project test'), () => {
+ expect(true).to.equal(true);
+});
+```
+
+**Configuration:**
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "multiproject": {
+ "enabled": true
+ }
+ }
+}
+```
+
+For detailed information, see the [Multi-Project Support Guide](docs/MULTI_PROJECT.md).
## Requirements
-We maintain the reporter on [LTS versions of Node](https://nodejs.org/en/about/releases/).
+- Node.js >= 14
+- WebdriverIO >= 8.40.0
+
+## Documentation
+
+| Guide | Description |
+|-------|-------------|
+| [Usage Guide](docs/usage.md) | Complete usage reference with all methods and options |
+| [Attachments](docs/ATTACHMENTS.md) | Adding screenshots, logs, and files to test results |
+| [Steps](docs/STEPS.md) | Defining test steps for detailed reporting |
+| [Multi-Project Support](docs/MULTI_PROJECT.md) | Reporting to multiple Qase projects |
+| [Upgrade Guide](docs/UPGRADE.md) | Migration guide for breaking changes |
+| [Configuration Reference](../qase-javascript-commons/README.md) | Full configuration options |
+
+## Examples
-`wdio >= 8.40.0`
+See the [examples directory](../examples/) for complete working examples:
+- [Mocha/Jasmine example](../examples/multiProject/wdio/)
+- Cucumber examples (check examples directory)
-
+## License
-[auth]: https://developers.qase.io/#authentication
+Apache License 2.0. See [LICENSE](LICENSE) for details.
diff --git a/qase-wdio/changelog.md b/qase-wdio/changelog.md
index a6950e50..db41b7ad 100644
--- a/qase-wdio/changelog.md
+++ b/qase-wdio/changelog.md
@@ -16,7 +16,7 @@
## What's new
- Fixed an issue where the test result was not uploaded to the Qase TMS when the test has skipped status.
-- Added a new function `qase` to set the test case ID.
+- Added a new function `Qase` to set the test case ID.
- Marked the function `qase.id` as deprecated.
# qase-wdio@1.1.0
diff --git a/qase-wdio/docs/ATTACHMENTS.md b/qase-wdio/docs/ATTACHMENTS.md
new file mode 100644
index 00000000..ac7f61f9
--- /dev/null
+++ b/qase-wdio/docs/ATTACHMENTS.md
@@ -0,0 +1,415 @@
+# Attachments in WebdriverIO
+
+This guide covers how to attach files, screenshots, logs, and other content to your Qase test results.
+
+---
+
+## Overview
+
+Qase WebdriverIO Reporter supports attaching various types of content to test results:
+
+- **Files** — Attach files from the filesystem
+- **Screenshots** — Attach images captured during test execution
+- **Logs** — Attach text logs or console output
+- **Binary data** — Attach any binary content from memory
+
+Attachments can be added to:
+- **Test cases** — Visible in the overall test result
+- **Test steps** — Visible in specific step results
+
+**Framework Support:**
+- **Mocha/Jasmine:** Programmatic attachment API via `qase.attach()`
+- **Cucumber:** Attachments added via Cucumber's native step attachment API are automatically captured
+
+---
+
+## Attaching Files (Mocha/Jasmine)
+
+### From File Path
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it('Test with file attachment', () => {
+ qase.attach({ paths: 'path/to/file.txt' });
+ expect(true).to.equal(true);
+ });
+});
+```
+
+### Multiple Files
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it('Test with multiple file attachments', () => {
+ qase.attach({ paths: ['path/to/file1.txt', 'path/to/file2.log'] });
+ expect(true).to.equal(true);
+ });
+});
+```
+
+---
+
+## Attaching Content from Memory (Mocha/Jasmine)
+
+### Text Content
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it('Test with text content attachment', async () => {
+ const logContent = 'Test execution log content';
+
+ qase.attach({
+ name: 'execution.log',
+ content: logContent,
+ type: 'text/plain',
+ });
+
+ expect(true).to.equal(true);
+ });
+});
+```
+
+### Binary Content (Screenshots)
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it('Test with screenshot attachment', async () => {
+ await browser.url('https://example.com');
+
+ const screenshot = await browser.takeScreenshot();
+
+ qase.attach({
+ name: 'page-screenshot.png',
+ content: screenshot,
+ type: 'image/png',
+ });
+
+ await expect($('#header')).toExist();
+ });
+});
+```
+
+### JSON Data
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it('Test with JSON data attachment', () => {
+ const testData = {
+ userId: 123,
+ username: 'testuser',
+ timestamp: new Date().toISOString(),
+ };
+
+ qase.attach({
+ name: 'test-data.json',
+ content: JSON.stringify(testData, null, 2),
+ type: 'application/json',
+ });
+
+ expect(true).to.equal(true);
+ });
+});
+```
+
+---
+
+## Attaching to Steps (Mocha/Jasmine)
+
+Attach content to a specific test step:
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it('Test with step-level attachments', async () => {
+ await qase.step('Navigate and capture screenshot', async (step) => {
+ await browser.url('https://example.com/login');
+
+ step.attach({
+ name: 'login-page.png',
+ content: await browser.takeScreenshot(),
+ type: 'image/png',
+ });
+ });
+
+ await qase.step('Perform action', async (step) => {
+ await $('#username').setValue('testuser');
+
+ step.attach({
+ name: 'action-log.txt',
+ content: 'Username entered successfully',
+ type: 'text/plain',
+ });
+ });
+ });
+});
+```
+
+---
+
+## Cucumber Attachments
+
+Cucumber attachments are automatically captured when using step attachments:
+
+```javascript
+const { Given, When, Then } = require('@cucumber/cucumber');
+
+When('I take a screenshot', async function() {
+ const screenshot = await browser.takeScreenshot();
+ // Cucumber's native attach method
+ this.attach(screenshot, 'image/png');
+});
+
+When('I log test data', function() {
+ const data = JSON.stringify({ testId: 123 }, null, 2);
+ this.attach(data, 'application/json');
+});
+```
+
+---
+
+## Automatic Screenshot Reporting
+
+WebdriverIO can be configured to automatically attach screenshots:
+
+```javascript
+// wdio.conf.js
+const WDIOQaseReporter = require('wdio-qase-reporter').default;
+
+exports.config = {
+ reporters: [[WDIOQaseReporter, {
+ disableWebdriverScreenshotsReporting: false, // Enable automatic screenshots
+ }]],
+
+ // ... other options
+};
+```
+
+When `disableWebdriverScreenshotsReporting` is `false`, WebDriver screenshots are automatically attached to test results.
+
+---
+
+## Method Reference
+
+### `qase.attach()` (Mocha/Jasmine)
+
+Attach content to the test case.
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `paths` | `string` or `string[]` | No* | Path(s) to file(s) to attach |
+| `content` | `string` or `Buffer` | No* | Content to attach |
+| `name` | `string` | No | Custom filename (auto-detected from path) |
+| `type` | `string` | No | MIME type (auto-detected from extension) |
+
+\* Either `paths` or `content` must be provided, but not both.
+
+**Basic usage:**
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+// Attach file by path
+qase.attach({ paths: 'path/to/file.txt' });
+
+// Attach content from memory
+qase.attach({
+ name: 'log.txt',
+ content: 'Test log content',
+ type: 'text/plain',
+});
+```
+
+### Step-level Attachment (Mocha/Jasmine)
+
+Within a step callback, use the step parameter:
+
+```javascript
+await qase.step('Step name', async (step) => {
+ step.attach({
+ name: 'step-attachment.txt',
+ content: 'Content for this step',
+ type: 'text/plain',
+ });
+});
+```
+
+---
+
+## MIME Types
+
+Common MIME types are auto-detected based on file extension:
+
+| Extension | MIME Type |
+|-----------|-----------|
+| `.png` | `image/png` |
+| `.jpg`, `.jpeg` | `image/jpeg` |
+| `.gif` | `image/gif` |
+| `.svg` | `image/svg+xml` |
+| `.txt` | `text/plain` |
+| `.log` | `text/plain` |
+| `.json` | `application/json` |
+| `.xml` | `application/xml` |
+| `.html` | `text/html` |
+| `.csv` | `text/csv` |
+| `.pdf` | `application/pdf` |
+| `.zip` | `application/zip` |
+
+For other file types, specify `type` explicitly.
+
+---
+
+## Common Use Cases
+
+### WebDriver Screenshots
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it('Test with WebDriver screenshot', async () => {
+ await browser.url('https://example.com');
+
+ // Take screenshot using WebDriver
+ const screenshot = await browser.takeScreenshot();
+
+ qase.attach({
+ name: 'page-state.png',
+ content: screenshot,
+ type: 'image/png',
+ });
+
+ await expect($('#main-content')).toExist();
+ });
+});
+```
+
+### Element Screenshots
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it('Test with element screenshot', async () => {
+ await browser.url('https://example.com');
+
+ // Screenshot specific element
+ const element = await $('#header');
+ const screenshot = await element.saveScreenshot();
+
+ qase.attach({
+ name: 'header-screenshot.png',
+ content: screenshot,
+ type: 'image/png',
+ });
+
+ await expect(element).toExist();
+ });
+});
+```
+
+### Browser Console Logs
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it('Test with browser console logs', async () => {
+ await browser.url('https://example.com');
+
+ // Get browser logs
+ const logs = await browser.getLogs('browser');
+
+ qase.attach({
+ name: 'console-logs.json',
+ content: JSON.stringify(logs, null, 2),
+ type: 'application/json',
+ });
+
+ expect(true).to.equal(true);
+ });
+});
+```
+
+### WebDriver Command Logs
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it('Test with command logs', async () => {
+ await browser.url('https://example.com');
+ await $('#login-button').click();
+
+ // Capture execution timeline
+ const commandLog = browser.commandList.map(cmd => ({
+ command: cmd.method,
+ endpoint: cmd.endpoint,
+ duration: cmd.duration,
+ }));
+
+ qase.attach({
+ name: 'command-log.json',
+ content: JSON.stringify(commandLog, null, 2),
+ type: 'application/json',
+ });
+
+ expect(true).to.equal(true);
+ });
+});
+```
+
+---
+
+## Troubleshooting
+
+### Attachments Not Appearing
+
+1. Verify the file path exists and is readable
+2. Check file permissions
+3. Enable debug logging to see upload status:
+ ```json
+ {
+ "debug": true
+ }
+ ```
+4. For Mocha/Jasmine, ensure `qase` is imported from `wdio-qase-reporter`
+5. For Cucumber, verify you're using `this.attach()` within step definitions
+
+### Large Files
+
+Large attachments may slow down test execution. Consider:
+- Compressing logs before attaching
+- Using selective logging (e.g., only on failure)
+- Setting reasonable size limits
+- Using `disableWebdriverScreenshotsReporting: true` to disable automatic screenshots
+
+### Binary Data Issues
+
+When attaching binary data, always specify:
+- `name` with appropriate extension
+- `type` if extension doesn't match content type
+
+### Cucumber Attachments Not Captured
+
+Ensure:
+- Reporter is properly configured in `wdio.conf.js`
+- `useCucumber: true` is set in reporter options
+- Using `this.attach()` method, not `qase.attach()`
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Steps Guide](STEPS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-wdio/docs/MULTI_PROJECT.md b/qase-wdio/docs/MULTI_PROJECT.md
index bb211e2b..b1417dc4 100644
--- a/qase-wdio/docs/MULTI_PROJECT.md
+++ b/qase-wdio/docs/MULTI_PROJECT.md
@@ -6,6 +6,8 @@ Qase WDIO Reporter supports sending test results to multiple Qase projects simul
* Different projects track the same functionality with different test case IDs
* You want to maintain separate test runs for different environments or teams
+---
+
## Configuration
For detailed configuration options, refer to the [qase-javascript-commons README](../../qase-javascript-commons/README.md#multi-project-support).
@@ -14,6 +16,33 @@ For detailed configuration options, refer to the [qase-javascript-commons README
Set `mode` to `testops_multi` in your WDIO config (e.g. in `wdio.conf.js` or `qase.config.json`) and add the `testops_multi` section with `default_project` and `projects`.
+**Example configuration:**
+
+```json
+{
+ "mode": "testops_multi",
+ "testops_multi": {
+ "default_project": "PROJ1",
+ "projects": [
+ {
+ "code": "PROJ1",
+ "api": {
+ "token": "your_api_token_for_proj1"
+ }
+ },
+ {
+ "code": "PROJ2",
+ "api": {
+ "token": "your_api_token_for_proj2"
+ }
+ }
+ ]
+ }
+}
+```
+
+---
+
## Using `qase.projects(mapping, name)`
Use `qase.projects(mapping, name)` to set the test title with multi-project markers. Use the returned string as the test name:
@@ -22,32 +51,122 @@ Use `qase.projects(mapping, name)` to set the test title with multi-project mark
const { qase } = require('wdio-qase-reporter');
// Single project
-it(qase(1, 'should work'), async () => { ... });
+it(qase(1, 'should work'), async () => {
+ await browser.url('https://example.com');
+ expect(await browser.getTitle()).toBe('Example Domain');
+});
// Multi-project
-it(qase.projects({ PROJ1: [100], PROJ2: [200] }, 'Login flow'), async () => { ... });
+it(qase.projects({ PROJ1: [100], PROJ2: [200] }, 'Login flow'), async () => {
+ await browser.url('https://example.com/login');
+ await $('#username').setValue('testuser');
+ await $('#login-button').click();
+});
// Multiple IDs per project
-it(qase.projects({ PROJ1: [10, 11], PROJ2: [20] }, 'Checkout'), async () => { ... });
+it(qase.projects({ PROJ1: [10, 11], PROJ2: [20] }, 'Checkout'), async () => {
+ await browser.url('https://example.com/cart');
+ await $('#checkout-button').click();
+});
```
+**Key points:**
+
+- Single project with single ID: `it(qase(100, 'test name'), async () => { ... })`
+- Multi-project: `it(qase.projects({ PROJ1: [100], PROJ2: [200] }, 'test name'), async () => { ... })`
+- Multiple IDs per project: `it(qase.projects({ PROJ1: [10, 11], PROJ2: [20] }, 'test name'), async () => { ... })`
+
Project codes (e.g. `PROJ1`, `PROJ2`) must match `testops_multi.projects[].code` in your config.
+---
+
## Tests Without Project Mapping
Tests that do not use `qase.projects()` and have no `(Qase PROJ: ids)` in the title are sent to the `default_project`. If they use `qase(id, name)` (single-project), that ID is used for the default project.
+---
+
## Important Notes
-1. **Project codes must match**: Codes in `qase.projects({ PROJ1: [1], ... })` must match `testops_multi.projects[].code`.
+1. **Project codes must match**: Codes in `qase.projects({ PROJ1: [1], ... })` must match `testops_multi.projects[].code` in config.
2. **Mode**: Set `mode` to `testops_multi` in WDIO reporter config.
3. **Title format**: The helper produces a title like `Name (Qase PROJ1: 1,2) (Qase PROJ2: 3)` so the reporter can parse the mapping.
+4. **API tokens**: Each project in `testops_multi.projects[]` can have its own API token for separate authentication.
+
+---
## Examples
See the [multi-project WDIO example](../../examples/multiProject/wdio/) for a complete runnable setup.
+### Complete Example
+
+Here's a complete WDIO test file showing multi-project usage:
+
+```javascript
+const { qase } = require('wdio-qase-reporter');
+
+describe('Multi-project test suite', () => {
+ // Test reported to two projects
+ it(qase.projects({ PROJ1: [1], PROJ2: [2] }, 'User can login successfully'), async () => {
+ await browser.url('https://example.com/login');
+ await $('#username').setValue('testuser');
+ await $('#password').setValue('password123');
+ await $('#login-button').click();
+
+ expect(await browser.getUrl()).toContain('dashboard');
+ });
+
+ // Test with multiple case IDs per project
+ it(qase.projects({ PROJ1: [10, 11], PROJ2: [20] }, 'Checkout process works'), async () => {
+ await browser.url('https://example.com/cart');
+ await $('#checkout-button').click();
+
+ const successMessage = await $('.success-message');
+ expect(await successMessage.isDisplayed()).toBe(true);
+ });
+
+ // Single-project test (uses default_project)
+ it(qase(50, 'Test reported to default project'), async () => {
+ await browser.url('https://example.com');
+ expect(await browser.getTitle()).toBe('Example Domain');
+ });
+
+ // Test without Qase metadata (goes to default_project without case ID)
+ it('Regular test without Qase tracking', async () => {
+ await browser.url('https://example.com');
+ expect(await browser.getTitle()).toBeTruthy();
+ });
+});
+```
+
+---
+
## Troubleshooting
-* Verify `mode` is `testops_multi` and project codes in `qase.projects()` match the config.
-* Ensure the test name is the string returned by `qase.projects(mapping, name)` (or an equivalent title with markers).
+### Results Not Appearing in All Projects
+
+* Verify `mode` is `testops_multi` (not `testops`) and project codes in `qase.projects()` match the config
+* Check that project codes match config codes exactly (case-sensitive)
+* Ensure each project has a valid API token with write permissions
+* Ensure the test name is the string returned by `qase.projects(mapping, name)`
+
+### Wrong Test Cases Linked
+
+* Verify the mapping object has correct project codes as keys
+* Check that test case IDs exist in the respective projects
+* Enable debug logging to see how the reporter parses multi-project markers
+
+### Default Project Not Working
+
+* Ensure `default_project` is set in `testops_multi` config
+* Verify the default project code matches one of the projects in the `projects` array
+* Tests without `qase.projects()` will only report to the default project
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Examples](../../examples/multiProject/wdio/)
diff --git a/qase-wdio/docs/STEPS.md b/qase-wdio/docs/STEPS.md
new file mode 100644
index 00000000..6a8007c4
--- /dev/null
+++ b/qase-wdio/docs/STEPS.md
@@ -0,0 +1,428 @@
+# Test Steps in WebdriverIO
+
+This guide covers how to define and report test steps for detailed execution tracking in Qase.
+
+---
+
+## Overview
+
+Test steps provide granular visibility into test execution. Each step is reported separately, showing:
+
+- Step name and description
+- Step status (passed/failed)
+- Step duration
+- Attachments (if any)
+- Error details (on failure)
+
+**Framework Support:**
+- **Mocha/Jasmine:** Programmatic steps via `qase.step()`
+- **Cucumber:** Gherkin steps (Given/When/Then) are automatically reported as Qase steps
+
+---
+
+## Defining Steps (Mocha/Jasmine)
+
+### Using Async Function
+
+Define steps as async functions with callbacks:
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it('Test with steps', async () => {
+ await qase.step('Navigate to login page', async () => {
+ await browser.url('https://example.com/login');
+ });
+
+ await qase.step('Enter credentials', async () => {
+ await $('#email').setValue('user@example.com');
+ await $('#password').setValue('password123');
+ });
+
+ await qase.step('Submit form', async () => {
+ await $('#login-button').click();
+ });
+
+ await qase.step('Verify login success', async () => {
+ await expect($('#dashboard')).toExist();
+ });
+ });
+});
+```
+
+### Step Parameters
+
+Steps can include parameters for dynamic naming:
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it('Test with parameterized steps', async () => {
+ const username = 'testuser';
+ const email = 'user@example.com';
+
+ await qase.step(`Login as ${username}`, async () => {
+ await $('#email').setValue(email);
+ await $('#password').setValue('password123');
+ await $('#login-button').click();
+ });
+
+ await qase.step(`Verify ${username} is logged in`, async () => {
+ await expect($('#user-profile')).toHaveText(username);
+ });
+ });
+});
+```
+
+---
+
+## Nested Steps (Mocha/Jasmine)
+
+Create hierarchical step structures using the step callback parameter:
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it('Test with nested steps', async () => {
+ await qase.step('Complete user registration', async (step) => {
+ await step.step('Fill personal information', async (step2) => {
+ await step2.step('Enter name', async () => {
+ await $('#firstName').setValue('John');
+ await $('#lastName').setValue('Doe');
+ });
+
+ await step2.step('Enter email', async () => {
+ await $('#email').setValue('john.doe@example.com');
+ });
+ });
+
+ await step.step('Fill address information', async () => {
+ await $('#address').setValue('123 Main St');
+ await $('#city').setValue('New York');
+ });
+
+ await step.step('Submit registration form', async () => {
+ await $('#submit-button').click();
+ });
+ });
+
+ await qase.step('Verify registration success', async () => {
+ await expect($('#success-message')).toExist();
+ });
+ });
+});
+```
+
+**Note:** WebdriverIO nested steps use the step callback parameter for nesting, not `qase.step()` directly.
+
+---
+
+## Cucumber Steps
+
+In Cucumber, Gherkin steps (Given/When/Then) are automatically reported as Qase test steps:
+
+```gherkin
+Feature: User Authentication
+
+ Scenario: User can log in
+ Given I am on the login page
+ When I enter valid credentials
+ And I click the login button
+ Then I should see the dashboard
+```
+
+Each Gherkin step is automatically captured and reported to Qase with:
+- Step name (from Gherkin step text)
+- Step status (passed/failed/skipped)
+- Step duration
+
+**No programmatic step API is needed for Cucumber** — Qase reporter automatically captures all Gherkin steps.
+
+---
+
+## Steps with Attachments (Mocha/Jasmine)
+
+Attach content to a specific step:
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it('Test with step attachments', async () => {
+ await qase.step('Navigate to page', async (step) => {
+ await browser.url('https://example.com');
+
+ step.attach({
+ name: 'page-loaded.png',
+ content: await browser.takeScreenshot(),
+ type: 'image/png',
+ });
+ });
+
+ await qase.step('Perform action', async (step) => {
+ await $('#action-button').click();
+
+ step.attach({
+ name: 'action-log.txt',
+ content: 'Button clicked successfully',
+ type: 'text/plain',
+ });
+ });
+ });
+});
+```
+
+---
+
+## Automatic WebDriver Step Reporting
+
+WebdriverIO can be configured to automatically report WebDriver commands as steps:
+
+```javascript
+// wdio.conf.js
+const WDIOQaseReporter = require('wdio-qase-reporter').default;
+
+exports.config = {
+ reporters: [[WDIOQaseReporter, {
+ disableWebdriverStepsReporting: false, // Enable automatic WebDriver step reporting
+ }]],
+
+ // ... other options
+};
+```
+
+When `disableWebdriverStepsReporting` is `false`, WebDriver commands (e.g., `browser.url()`, `$('#element').click()`) are automatically reported as Qase steps.
+
+---
+
+## Step Status
+
+Steps automatically inherit status from execution:
+
+| Execution | Step Status |
+|-----------|-------------|
+| Completes normally | Passed |
+| Throws Error/AssertionError | Failed |
+| Other exception | Invalid |
+
+---
+
+## Best Practices
+
+### Keep Steps Atomic
+
+Each step should represent a single action:
+
+```javascript
+// Good: One action per step
+await qase.step('Click login button', async () => {
+ await $('#login-btn').click();
+});
+
+await qase.step('Enter username', async () => {
+ await $('#username').setValue('user');
+});
+
+// Avoid: Multiple actions in one step
+await qase.step('Fill form and submit', async () => { // Too broad
+ await $('#username').setValue('user');
+ await $('#password').setValue('pass');
+ await $('#submit').click();
+});
+```
+
+### Use Descriptive Names
+
+```javascript
+// Good: Clear action description
+await qase.step('Verify user is redirected to dashboard', async () => {
+ await expect(browser).toHaveUrl('https://example.com/dashboard');
+});
+
+// Avoid: Vague names
+await qase.step('Check page', async () => {
+ await expect(browser).toHaveUrl('https://example.com/dashboard');
+});
+```
+
+### Include Context in Step Names
+
+```javascript
+// Good: Include relevant context
+const productName = 'Laptop';
+await qase.step(`Add product '${productName}' to cart`, async () => {
+ await $(`#product-${productName} .add-to-cart`).click();
+});
+
+// Better than generic:
+await qase.step('Add product', async () => {
+ await $(`#product-${productName} .add-to-cart`).click();
+});
+```
+
+---
+
+## Common Patterns
+
+### Page Object Steps
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+class LoginPage {
+ get emailInput() { return $('#email'); }
+ get passwordInput() { return $('#password'); }
+ get loginButton() { return $('#login-button'); }
+
+ async login(email, password) {
+ await qase.step('Navigate to login page', async () => {
+ await browser.url('/login');
+ });
+
+ await qase.step(`Enter email: ${email}`, async () => {
+ await this.emailInput.setValue(email);
+ });
+
+ await qase.step('Enter password', async () => {
+ await this.passwordInput.setValue(password);
+ });
+
+ await qase.step('Click login button', async () => {
+ await this.loginButton.click();
+ });
+ }
+}
+
+describe('Login Tests', () => {
+ it('User can log in', async () => {
+ const loginPage = new LoginPage();
+ await loginPage.login('user@example.com', 'password123');
+
+ await qase.step('Verify login success', async () => {
+ await expect($('#dashboard')).toExist();
+ });
+ });
+});
+```
+
+### Browser Interaction Steps
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it('Test with browser interaction steps', async () => {
+ await qase.step('Navigate to page', async () => {
+ await browser.url('https://example.com');
+ });
+
+ await qase.step('Interact with dropdown', async (step) => {
+ await step.step('Open dropdown', async () => {
+ await $('.dropdown-trigger').click();
+ });
+
+ await step.step('Select option', async () => {
+ await $('.dropdown-menu .first-item').click();
+ });
+ });
+
+ await qase.step('Verify selection', async () => {
+ await expect($('.selected-value')).toHaveText('First Item');
+ });
+ });
+});
+```
+
+### Setup/Teardown Steps
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Test Suite', () => {
+ it('Test with setup and teardown', async () => {
+ await qase.step('Setup: Create test data', async () => {
+ await browser.execute(() => {
+ localStorage.setItem('testData', JSON.stringify({ userId: 123 }));
+ });
+ });
+
+ await qase.step('Execute main test flow', async () => {
+ await browser.url('/dashboard');
+ await expect($('#user-id')).toHaveText('123');
+ });
+
+ await qase.step('Teardown: Clean up test data', async () => {
+ await browser.execute(() => {
+ localStorage.removeItem('testData');
+ });
+ });
+ });
+});
+```
+
+---
+
+## Troubleshooting
+
+### Steps Not Appearing (Mocha/Jasmine)
+
+1. Verify the step function is properly imported from `wdio-qase-reporter`
+2. Check that steps are executed within a test context
+3. Enable debug logging to trace step recording
+4. Ensure you're using `await` with async step callbacks
+
+### Steps Not Appearing (Cucumber)
+
+1. Verify reporter is configured correctly in `wdio.conf.js`
+2. Check that `useCucumber: true` is set in reporter options
+3. Ensure Gherkin steps are properly defined in feature files
+4. Enable debug logging to see step capture
+
+### Nested Steps Flattened
+
+Ensure you're using the callback parameter correctly for nesting:
+
+```javascript
+// Correct: Nested via callback parameter
+await qase.step('Parent step', async (step) => {
+ await step.step('Child step', async () => {
+ // Child step logic
+ });
+});
+
+// Incorrect: This creates sequential steps, not nested
+await qase.step('Step 1', async () => {
+ // Step 1 logic
+});
+await qase.step('Step 2', async () => { // Not nested under Step 1
+ // Step 2 logic
+});
+```
+
+### Too Many Automatic Steps
+
+If automatic WebDriver step reporting creates too many steps, disable it:
+
+```javascript
+// wdio.conf.js
+exports.config = {
+ reporters: [[WDIOQaseReporter, {
+ disableWebdriverStepsReporting: true, // Disable automatic steps
+ }]],
+};
+```
+
+### Step Duration Shows 0
+
+Steps need measurable execution time. Very fast steps may show 0ms duration.
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Attachments Guide](ATTACHMENTS.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
diff --git a/qase-wdio/docs/UPGRADE.md b/qase-wdio/docs/UPGRADE.md
new file mode 100644
index 00000000..0ff7428f
--- /dev/null
+++ b/qase-wdio/docs/UPGRADE.md
@@ -0,0 +1,449 @@
+# Upgrade Guide: WDIO Reporter
+
+This guide covers migration steps between major versions of the Qase WDIO Reporter.
+
+---
+
+## Version History
+
+| Version | Release Date | Node.js Support | Key Changes |
+|---------|--------------|-----------------|-------------|
+| 1.2.0 | January 2026 | >= 14 | Current stable release with enhanced Cucumber integration |
+| 1.1.0 | December 2025 | >= 14 | Multi-project support and improved step handling |
+| 1.0.0 | August 2025 | >= 14 | Initial release with unified qase-javascript-commons |
+
+---
+
+## Upgrading to 1.x
+
+### Breaking Changes
+
+The wdio-qase-reporter is currently in its first major version series (1.x). No migration from a previous major version is required.
+
+### Current Version Features
+
+Version 1.2.0 includes:
+
+- Full support for Qase TestOps API with batch result upload
+- Dual framework support: Mocha/Jasmine and Cucumber
+- Test case linking via wrapper function or Gherkin tags
+- Rich metadata support: titles, fields, suites, parameters, comments
+- Step reporting for both Mocha/Jasmine and Cucumber modes
+- File and content-based attachments
+- Multi-project support for reporting to multiple Qase projects
+- Flexible configuration via `wdio.conf.js` or `qase.config.json`
+
+---
+
+## Configuration
+
+### Current Format (v1.x)
+
+Configuration uses the modern qase-javascript-commons format:
+
+**wdio.conf.js (Mocha/Jasmine):**
+
+```javascript
+exports.config = {
+ framework: 'mocha', // or 'jasmine'
+ reporters: [
+ 'spec',
+ [
+ 'qase',
+ {
+ mode: 'testops',
+ debug: false,
+ testops: {
+ api: {
+ token: process.env.QASE_API_TOKEN,
+ },
+ project: 'DEMO',
+ run: {
+ title: 'WDIO Automated Run',
+ description: 'Test run from CI/CD pipeline',
+ complete: true,
+ },
+ batch: {
+ size: 100,
+ },
+ },
+ },
+ ],
+ ],
+ // ... other WDIO config
+};
+```
+
+**wdio.conf.js (Cucumber):**
+
+```javascript
+exports.config = {
+ framework: 'cucumber',
+ reporters: [
+ 'spec',
+ [
+ 'qase',
+ {
+ mode: 'testops',
+ testops: {
+ api: { token: process.env.QASE_API_TOKEN },
+ project: 'DEMO',
+ },
+ },
+ ],
+ ],
+ cucumberOpts: {
+ require: ['./step-definitions/**/*.js'],
+ },
+ // ... other WDIO config
+};
+```
+
+**Alternative: qase.config.json**
+
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "api": {
+ "token": "api_token_here"
+ },
+ "project": "DEMO",
+ "run": {
+ "complete": true
+ }
+ }
+}
+```
+
+---
+
+## Framework-Specific Usage
+
+### Mocha/Jasmine Mode
+
+**Import Pattern:**
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+```
+
+**Test Case ID Linking:**
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('User Authentication', () => {
+ it(qase(1, 'User can login'), async () => {
+ await browser.url('https://example.com/login');
+ await $('#username').setValue('user@example.com');
+ await $('#password').setValue('password123');
+ await $('#login-button').click();
+
+ await expect($('.dashboard')).toBeDisplayed();
+ });
+
+ it(qase([1, 2], 'Multiple IDs'), async () => {
+ // Test code
+ });
+});
+```
+
+**Metadata:**
+
+```javascript
+it('Test with metadata', async () => {
+ qase.title('Custom test title');
+ qase.fields({ severity: 'critical', priority: 'high', layer: 'e2e' });
+ qase.suite('Authentication / Login');
+
+ // Test code
+});
+```
+
+**Steps:**
+
+```javascript
+it('Test with steps', async () => {
+ await qase.step('Navigate to login page', async () => {
+ await browser.url('https://example.com/login');
+ });
+
+ await qase.step('Enter credentials', async () => {
+ await $('#username').setValue('user@example.com');
+ await $('#password').setValue('password123');
+ });
+
+ await qase.step('Verify login success', async () => {
+ await expect($('.dashboard')).toBeDisplayed();
+ });
+});
+```
+
+**Attachments:**
+
+```javascript
+// Path-based
+await qase.attach({ paths: '/path/to/log.txt' });
+
+// Content-based
+await qase.attach({
+ name: 'screenshot.png',
+ content: await browser.takeScreenshot(),
+ contentType: 'image/png',
+});
+```
+
+---
+
+### Cucumber Mode
+
+**Gherkin Tags:**
+
+```gherkin
+Feature: User Authentication
+ As a user
+ I want to log in to the application
+ So I can access my account
+
+ @QaseID=1
+ Scenario: Successful login with valid credentials
+ Given I am on the login page
+ When I enter valid credentials
+ And I click the login button
+ Then I should see the dashboard
+
+ @QaseID=2
+ @QaseFields={'severity':'high','priority':'critical'}
+ Scenario: Failed login with invalid credentials
+ Given I am on the login page
+ When I enter invalid credentials
+ And I click the login button
+ Then I should see an error message
+```
+
+**Step Definitions:**
+
+```javascript
+import { Given, When, Then } from '@wdio/cucumber-framework';
+
+Given('I am on the login page', async () => {
+ await browser.url('https://example.com/login');
+});
+
+When('I enter valid credentials', async () => {
+ await $('#username').setValue('user@example.com');
+ await $('#password').setValue('password123');
+});
+
+Then('I should see the dashboard', async () => {
+ await expect($('.dashboard')).toBeDisplayed();
+});
+```
+
+**Attachments in Cucumber:**
+
+```javascript
+import { Given } from '@wdio/cucumber-framework';
+
+Given('I take a screenshot', async function () {
+ const screenshot = await browser.takeScreenshot();
+ await this.attach(screenshot, 'image/png');
+});
+```
+
+---
+
+## Reporter-Specific Options
+
+WDIO reporter includes additional configuration options:
+
+```javascript
+reporters: [
+ [
+ 'qase',
+ {
+ mode: 'testops',
+ testops: {
+ api: { token: 'token' },
+ project: 'DEMO',
+ },
+ // WDIO-specific options
+ disableWebdriverStepsReporting: false, // Report WebDriver commands as steps
+ disableWebdriverScreenshotsReporting: false, // Report screenshots
+ useCucumber: false, // Enable Cucumber integration (auto-detected)
+ },
+ ],
+],
+```
+
+**Options:**
+
+- `disableWebdriverStepsReporting`: Disable automatic reporting of WebDriver commands as steps
+- `disableWebdriverScreenshotsReporting`: Disable automatic screenshot attachment
+- `useCucumber`: Enable Cucumber mode (usually auto-detected from framework config)
+
+---
+
+## Compatibility Notes
+
+### Node.js Version Support
+
+- **Current (1.2.0):** Node.js >= 14
+
+### WebdriverIO Version Support
+
+- **Current (1.2.0):** WebdriverIO >= 8.40.0
+- Tested with WDIO 8.x and 9.x
+
+### Framework Compatibility
+
+- **Mocha:** Full support for wrapper function pattern, metadata, steps, attachments
+- **Jasmine:** Full support for wrapper function pattern, metadata, steps, attachments
+- **Cucumber:** Tag-based linking, native Gherkin steps, native attachments
+- ES Modules recommended
+- CommonJS supported
+- TypeScript support with full type definitions
+
+---
+
+## Troubleshooting
+
+### Common Issues
+
+#### Issue: Reporter not recognized
+
+**Solution:** Ensure the reporter is installed and configured:
+
+```bash
+npm install --save-dev wdio-qase-reporter
+```
+
+```javascript
+// wdio.conf.js
+reporters: [
+ ['qase', { /* options */ }],
+],
+```
+
+#### Issue: Mocha/Jasmine mode - wrapper function not working
+
+**Solution:** Verify import path and usage:
+
+```javascript
+// Correct
+import { qase } from 'wdio-qase-reporter';
+
+describe('Tests', () => {
+ it(qase(1, 'Test name'), async () => { /* ... */ });
+});
+
+// Incorrect - missing qase wrapper
+it('Test name', async () => { /* ... */ });
+```
+
+#### Issue: Cucumber mode - tags not recognized
+
+**Solution:** Check tag syntax in feature files:
+
+```gherkin
+# Correct
+@QaseID=1
+Scenario: Test scenario
+
+# Incorrect - missing equals sign
+@QaseID 1
+Scenario: Test scenario
+```
+
+#### Issue: Steps not reporting in Mocha/Jasmine mode
+
+**Solution:** Use `qase.step()` with async callbacks:
+
+```javascript
+// Correct
+await qase.step('Step name', async () => {
+ await browser.url('https://example.com');
+});
+
+// Incorrect - missing await
+qase.step('Step name', async () => {
+ await browser.url('https://example.com');
+});
+```
+
+#### Issue: Cucumber attachments not appearing
+
+**Solution:** Use native `this.attach()` in step definitions:
+
+```javascript
+// Correct - in step definition
+When('I capture screenshot', async function () {
+ const screenshot = await browser.takeScreenshot();
+ await this.attach(screenshot, 'image/png');
+});
+
+// Incorrect - qase.attach() not supported in Cucumber mode
+When('I capture screenshot', async () => {
+ const screenshot = await browser.takeScreenshot();
+ await qase.attach({ content: screenshot }); // Error
+});
+```
+
+#### Issue: Configuration not recognized
+
+**Solution:** Verify configuration structure in `wdio.conf.js`:
+
+```javascript
+reporters: [
+ [
+ 'qase',
+ {
+ mode: 'testops',
+ testops: {
+ api: { token: process.env.QASE_API_TOKEN },
+ project: 'YOUR_PROJECT_CODE',
+ },
+ },
+ ],
+],
+```
+
+Or use `qase.config.json`:
+
+```json
+{
+ "mode": "testops",
+ "testops": {
+ "api": { "token": "your_token" },
+ "project": "YOUR_PROJECT_CODE"
+ }
+}
+```
+
+---
+
+## Getting Help
+
+If you encounter issues:
+
+1. Check the [GitHub Issues](https://github.com/qase-tms/qase-javascript/issues)
+2. Review the CHANGELOG
+3. Open a new issue with:
+ - Current version (1.2.0)
+ - WebdriverIO version
+ - Framework (Mocha/Jasmine/Cucumber)
+ - Error messages
+ - Configuration file (without sensitive data)
+ - Test code example
+ - Steps to reproduce
+
+---
+
+## See Also
+
+- [Usage Guide](usage.md)
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Multi-Project Support](MULTI_PROJECT.md)
+- CHANGELOG
+- [WebdriverIO Documentation](https://webdriver.io/docs/gettingstarted)
diff --git a/qase-wdio/docs/usage.md b/qase-wdio/docs/usage.md
index ba83f9e7..ade7184b 100644
--- a/qase-wdio/docs/usage.md
+++ b/qase-wdio/docs/usage.md
@@ -1,236 +1,451 @@
# Qase Integration in WebdriverIO
-This guide demonstrates how to integrate Qase with WebdriverIO, providing instructions on how to add Qase IDs, titles,
-fields, suites, comments, and file attachments to your test cases.
+This guide provides comprehensive instructions for integrating Qase with WebdriverIO (WDIO), covering both Mocha/Jasmine and Cucumber frameworks.
+> **Configuration:** For complete configuration reference including all available options, environment variables, and examples, see the [qase-javascript-commons README](../../qase-javascript-commons/README.md).
+
+---
+
+## Table of Contents
+
+- [Adding QaseID](#adding-qaseid)
+- [Adding Title](#adding-title)
+- [Adding Fields](#adding-fields)
+- [Adding Suite](#adding-suite)
+- [Ignoring Tests](#ignoring-tests)
+- [Muting Tests](#muting-tests)
+- [Adding Comments](#adding-comments)
+- [Working with Attachments](#working-with-attachments)
+- [Working with Steps](#working-with-steps)
+- [Working with Parameters](#working-with-parameters)
+- [Cucumber Integration](#cucumber-integration)
+- [Running Tests](#running-tests)
+- [Integration Patterns](#integration-patterns)
+- [Common Use Cases](#common-use-cases)
+- [Troubleshooting](#troubleshooting)
+- [Complete Examples](#complete-examples)
+
+- [See Also](#see-also)
---
-## Adding QaseID to a Test
+## Adding QaseID
-To associate a QaseID with a test in WebdriverIO, use the `qase` function. This function accepts a single integer
-representing the test's ID in Qase.
+Link your WebdriverIO tests to existing test cases in Qase by specifying the test case ID.
-### Example
+### Mocha/Jasmine - Single ID
```javascript
import { qase } from 'wdio-qase-reporter';
-it(qase(1, 'test'), () => {
- browser.url('https://example.com');
+describe('Authentication', () => {
+ it(qase(1, 'User can log in'), () => {
+ expect(true).to.equal(true);
+ });
});
+```
+
+### Mocha/Jasmine - Multiple IDs
-it(qase([1, 2, 3], 'test'), () => {
- browser.url('https://example.com');
+```javascript
+describe('User Management', () => {
+ it(qase([1, 2, 3], 'CRUD operations'), () => {
+ expect(true).to.equal(true);
+ });
});
```
+### Cucumber - Tags
+
+```gherkin
+Feature: User Authentication
+
+ @QaseId=1
+ Scenario: User can log in
+ Given I am on the login page
+ When I enter valid credentials
+ Then I should see the dashboard
+
+ @QaseId=2,3,4
+ Scenario: Multiple test cases
+ Given I am logged in
+ When I perform actions
+ Then I should see results
+```
+
---
-## Adding a Title to a Test
+## Adding Title
-You can provide a title for your test using the `qase.title` function. The function accepts a string, which will be
-used as the test's title in Qase. If no title is provided, the test method name will be used by default.
+Set a custom title for the test case (overrides auto-generated title).
-### Example
+### Mocha/Jasmine
```javascript
import { qase } from 'wdio-qase-reporter';
-it('test', () => {
- qase.title('Title');
- browser.url('https://example.com');
+it('Login test', () => {
+ qase.title('User can successfully log in with valid email and password');
+ browser.url('/login');
+ expect(true).to.equal(true);
});
```
+### Cucumber
+
+```gherkin
+@QaseId=1
+@Title=User can successfully log in with valid credentials
+Scenario: Login test
+ Given I am on the login page
+```
+
---
-## Adding Fields to a Test
+## Adding Fields
-The `qase.fields` function allows you to add additional metadata to a test case. You can specify multiple fields to
-enhance test case information in Qase.
+Add metadata to your test cases using fields. Both system and custom fields are supported.
### System Fields
-- `description` — Description of the test case.
-- `preconditions` — Preconditions for the test case.
-- `postconditions` — Postconditions for the test case.
-- `severity` — Severity of the test case (e.g., `critical`, `major`).
-- `priority` — Priority of the test case (e.g., `high`, `low`).
-- `layer` — Test layer (e.g., `UI`, `API`).
+| Field | Description | Example Values |
+|-------|-------------|----------------|
+| `description` | Test case description | Any text |
+| `preconditions` | Test preconditions | Any text (supports Markdown) |
+| `postconditions` | Test postconditions | Any text |
+| `severity` | Test severity | `blocker`, `critical`, `major`, `normal`, `minor`, `trivial` |
+| `priority` | Test priority | `high`, `medium`, `low` |
+| `layer` | Test layer | `e2e`, `api`, `unit` |
-### Example
+### Mocha/Jasmine Example
```javascript
import { qase } from 'wdio-qase-reporter';
-it('test', () => {
- qase.fields({ description: "Description", preconditions: "Preconditions" });
- browser.url('https://example.com');
+it('Critical login test', () => {
+ qase.fields({
+ description: 'Verifies that users can log in with valid credentials',
+ preconditions: 'User account must exist in the system',
+ severity: 'critical',
+ priority: 'high',
+ layer: 'e2e',
+ });
+
+ browser.url('/login');
+ expect(true).to.equal(true);
+});
+```
+
+### Cucumber Example
+
+Cucumber does not support fields via tags. Use fields in step definitions:
+
+```javascript
+// In step definition file
+import { qase } from 'wdio-qase-reporter';
+
+Given('I have set test metadata', () => {
+ qase.fields({
+ severity: 'high',
+ priority: 'critical',
+ });
});
```
---
-## Adding a Suite to a Test
+## Adding Suite
-To assign a suite or sub-suite to a test, use the `qase.suite` function. It can receive a suite name, and optionally a
-sub-suite, both as strings.
+Organize tests into suites and sub-suites.
-### Example
+### Simple Suite
```javascript
import { qase } from 'wdio-qase-reporter';
-it('test', () => {
- qase.suite("Suite 01");
- browser.url('https://example.com');
+it('Test with suite', () => {
+ qase.suite('Authentication');
+ expect(true).to.equal(true);
});
+```
-it('test', () => {
- qase.suite("Suite 01\tSuite 02");
- browser.url('https://example.com');
+### Nested Suites
+
+```javascript
+it('Test with nested suite', () => {
+ qase.suite('Authentication\tLogin\tValid Credentials');
+ expect(true).to.equal(true);
});
```
+### Cucumber
+
+```gherkin
+@QaseId=1
+@Suite=Authentication
+Scenario: Login test
+ Given I am on the login page
+```
+
---
-## Ignoring a Test in Qase
+## Ignoring Tests
-To exclude a test from being reported to Qase (while still executing the test in WebdriverIO), use the `qase.ignore`
-function. The test will run, but its result will not be sent to Qase.
+Exclude a test from Qase reporting. The test still executes, but results are not sent to Qase.
-### Example
+### Mocha/Jasmine
```javascript
import { qase } from 'wdio-qase-reporter';
-it('test', () => {
+it('Ignored test', () => {
qase.ignore();
- browser.url('https://example.com');
+ expect(true).to.equal(true);
});
```
+### Cucumber
+
+Cucumber does not support ignore functionality via tags. Filter tests using standard Cucumber tag filtering.
+
---
-## Adding a Comment to a Test
+## Muting Tests
-You can attach comments to the test results in Qase using the `qase.comment` function. The comment will be displayed
-alongside the test execution details in Qase.
+Mark a test as muted. Muted tests are reported but do not affect the test run status.
-### Example
+### Mocha/Jasmine
```javascript
import { qase } from 'wdio-qase-reporter';
-it('test', () => {
- qase.comment("Some comment");
- browser.url('https://example.com');
+it(qase(1, 'Known failing test'), () => {
+ qase.mute();
+ expect(false).to.equal(true); // This failure won't affect the run status
});
```
+### Cucumber
+
+```gherkin
+Feature: User Authentication
+
+ @QaseMuted
+ Scenario: Known failing test
+ Given I am on the login page
+ When I enter invalid credentials
+ Then I should see an error
+```
+
---
-## Attaching Files to a Test
+## Adding Comments
-To attach files to a test result, use the `qase.attach` function. This method supports attaching one or multiple files,
-along with optional file names, comments, and file types.
+Attach comments to test results in Qase.
-### Example
+### Mocha/Jasmine
```javascript
import { qase } from 'wdio-qase-reporter';
-it('test', () => {
- qase.attach({ name: 'attachment.txt', content: 'Hello, world!', type: 'text/plain' });
- qase.attach({ paths: '/path/to/file' });
- qase.attach({ paths: ['/path/to/file', '/path/to/another/file'] });
- browser.url('https://example.com');
+it('Test with comment', () => {
+ qase.comment('This test verifies edge case behavior for special characters in input');
+ expect(true).to.equal(true);
});
```
-## Adding Parameters to a Test
+---
+
+## Working with Attachments
-You can add parameters to a test case using the `qase.parameters` function. This function accepts an object with
-parameter names and values.
+Attach files, screenshots, logs, and other content to your test results.
-### Example
+### Attach File from Path
```javascript
import { qase } from 'wdio-qase-reporter';
-it('test', () => {
- qase.parameters({ param1: 'value1', param2: 'value2' });
- browser.url('https://example.com');
+it('Test with file attachment', () => {
+ qase.attach({ paths: '/path/to/log.txt' });
+ expect(true).to.equal(true);
});
```
-## Adding Group Parameters to a Test
+### Attach Multiple Files
-To add group parameters to a test case, use the `qase.groupParameters` function. This function accepts an object with
-group parameter names and values.
+```javascript
+it('Test with multiple attachments', () => {
+ qase.attach({
+ paths: [
+ '/path/to/log1.txt',
+ '/path/to/log2.txt',
+ '/path/to/screenshot.png',
+ ]
+ });
+ expect(true).to.equal(true);
+});
+```
-### Example
+### Attach Content from Code
```javascript
-import { qase } from 'wdio-qase-reporter';
+it('Test with content attachment', async () => {
+ const screenshot = await browser.takeScreenshot();
-it('test', () => {
- qase.parameters({ param1: 'value1', param2: 'value2' });
- qase.groupParameters({ param3: 'value3', param4: 'value4' });
- browser.url('https://example.com');
+ qase.attach({
+ name: 'screenshot.png',
+ content: screenshot,
+ type: 'image/png',
+ });
+
+ expect(true).to.equal(true);
});
```
-## Adding Steps to a Test
+### Attach to Specific Step
+
+```javascript
+it('Test with step attachments', async () => {
+ await qase.step('Capture state', async (step) => {
+ const screenshot = await browser.takeScreenshot();
+
+ step.attach({
+ name: 'current-state.png',
+ content: screenshot,
+ type: 'image/png',
+ });
+ });
+});
+```
+
+### Automatic WebDriver Screenshots
+
+Configure automatic screenshot attachment in `wdio.conf.js`:
+
+```javascript
+exports.config = {
+ reporters: [[WDIOQaseReporter, {
+ disableWebdriverScreenshotsReporting: false, // Enable auto-screenshots
+ }]],
+};
+```
+
+---
+
+## Working with Steps
-You can add steps to a test case using the `qase.step` function. This function accepts a string and a callback function,
-which will be used as the step description and actions in Qase.
+Define test steps for detailed reporting in Qase.
-### Example
+### Basic Steps
```javascript
import { qase } from 'wdio-qase-reporter';
-it('test', async () => {
- await qase.step('Some step', async (step) => {
- // some actions
- step.attach({ name: 'screenshot.png', type: 'image/png', content: await browser.takeScreenshot() });
+it('Test with steps', async () => {
+ await qase.step('Navigate to login page', async () => {
+ await browser.url('/login');
+ });
+
+ await qase.step('Enter credentials', async () => {
+ await $('#email').setValue('user@example.com');
+ await $('#password').setValue('password123');
+ });
+
+ await qase.step('Click login button', async () => {
+ await $('#login-button').click();
+ });
+
+ await qase.step('Verify successful login', async () => {
+ await expect($('#dashboard')).toExist();
});
- browser.url('https://example.com');
});
```
-## Cucumber Integration
+### Nested Steps
+
+```javascript
+it('Test with nested steps', async () => {
+ await qase.step('Complete registration flow', async (s1) => {
+ await s1.step('Fill registration form', async (s2) => {
+ await s2.step('Enter email', async () => {
+ await $('#email').setValue('user@example.com');
+ });
+
+ await s2.step('Enter password', async () => {
+ await $('#password').setValue('password123');
+ });
+ });
+
+ await s1.step('Submit form', async () => {
+ await $('#submit-button').click();
+ });
+
+ await s1.step('Verify registration', async () => {
+ await expect($('#success-message')).toExist();
+ });
+ });
+});
+```
-WebdriverIO Qase reporter supports Cucumber integration. When using Cucumber, you can annotate your scenarios with Qase IDs using tags.
+### Disable Automatic WebDriver Steps
-### Feature file example
+By default, WebDriver commands are automatically reported as steps. To disable this:
-```gherkin
-Feature: Test user role
+```javascript
+// wdio.conf.js
+exports.config = {
+ reporters: [[WDIOQaseReporter, {
+ disableWebdriverStepsReporting: true, // Only report custom steps
+ }]],
+};
+```
- @QaseId=3
- Scenario: Login
- Given I test login
- When I enter credentials
- Then I should be logged in
+---
- @QaseId=4,5
- @Title=Custom Test Title
- @Suite=Authentication
- Scenario: Logout
- Given I am logged in
- When I click logout
- Then I should be logged out
+## Working with Parameters
+
+Report parameterized test data to Qase.
+
+### Basic Parameters
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+it('Parameterized test', () => {
+ qase.parameters({
+ 'browser': 'chrome',
+ 'environment': 'staging',
+ 'resolution': '1920x1080',
+ });
+
+ expect(true).to.equal(true);
+});
```
-### Supported Cucumber tags
+### Group Parameters
-- `@QaseId=` - Set Qase test case ID(s). Multiple IDs can be separated by commas
-- `@Title=` - Set custom test title
-- `@Suite=` - Set test suite name
+```javascript
+it('Test with grouped parameters', () => {
+ qase.parameters({
+ 'browser': 'chrome',
+ 'os': 'macOS',
+ 'environment': 'staging',
+ });
-### Configuration for Cucumber
+ qase.groupParameters({
+ 'environment': 'staging',
+ });
+
+ expect(true).to.equal(true);
+});
+```
+
+---
+
+## Cucumber Integration
+
+WebdriverIO Qase reporter fully supports Cucumber integration.
+
+### Enable Cucumber Support
```javascript
// wdio.conf.js
@@ -242,6 +457,756 @@ exports.config = {
disableWebdriverStepsReporting: true,
disableWebdriverScreenshotsReporting: false,
}]],
+ framework: 'cucumber',
// ... other options
};
```
+
+### Feature File Example
+
+```gherkin
+Feature: User Authentication
+
+ @QaseId=1
+ @Title=User can log in with valid credentials
+ @Suite=Authentication
+ Scenario: Successful login
+ Given I am on the login page
+ When I enter email "user@example.com"
+ And I enter password "password123"
+ And I click the login button
+ Then I should see the dashboard
+
+ @QaseId=2,3
+ Scenario: Login with multiple test cases
+ Given I am on the login page
+ When I enter invalid credentials
+ Then I should see an error message
+```
+
+### Supported Cucumber Tags
+
+- `@QaseId=` - Set Qase test case ID(s). Multiple IDs separated by commas: `@QaseId=1,2,3`
+- `@Title=` - Set custom test title
+- `@Suite=` - Set test suite name
+
+### Cucumber Step Definitions
+
+```javascript
+// step-definitions/login.steps.js
+import { Given, When, Then } from '@wdio/cucumber-framework';
+
+Given('I am on the login page', async () => {
+ await browser.url('/login');
+});
+
+When('I enter email {string}', async (email) => {
+ await $('#email').setValue(email);
+});
+
+When('I enter password {string}', async (password) => {
+ await $('#password').setValue(password);
+});
+
+When('I click the login button', async () => {
+ await $('#login-button').click();
+});
+
+Then('I should see the dashboard', async () => {
+ await expect($('#dashboard')).toExist();
+});
+```
+
+---
+
+## Running Tests
+
+### Basic Execution
+
+```bash
+# Run all tests
+QASE_MODE=testops npx wdio run wdio.conf.js
+
+# Run specific spec file
+QASE_MODE=testops npx wdio run wdio.conf.js --spec ./test/specs/login.spec.js
+
+# Run specific suite
+QASE_MODE=testops npx wdio run wdio.conf.js --suite smoke
+```
+
+### Cucumber
+
+```bash
+# Run all Cucumber features
+QASE_MODE=testops npx wdio run wdio.conf.js
+
+# Run specific feature file
+QASE_MODE=testops npx wdio run wdio.conf.js --spec ./test/features/login.feature
+
+# Run scenarios with specific tag
+QASE_MODE=testops npx wdio run wdio.conf.js --cucumberOpts.tagExpression='@smoke'
+```
+
+### Multiple Browsers
+
+Configure in `wdio.conf.js`:
+
+```javascript
+exports.config = {
+ capabilities: [
+ {
+ browserName: 'chrome',
+ },
+ {
+ browserName: 'firefox',
+ },
+ ],
+};
+```
+
+### Parallel Execution
+
+```bash
+# Run with max instances
+QASE_MODE=testops npx wdio run wdio.conf.js --maxInstances 5
+```
+
+Configure in `wdio.conf.js`:
+
+```javascript
+exports.config = {
+ maxInstances: 5,
+};
+```
+
+### With Environment Variables
+
+```bash
+# Override configuration
+QASE_MODE=testops \
+QASE_TESTOPS_PROJECT=DEMO \
+QASE_TESTOPS_API_TOKEN=your_token \
+npx wdio run wdio.conf.js
+```
+
+---
+
+## Troubleshooting
+
+### Reporter Not Found
+
+**Problem:** Reporter not loading or tests run without Qase reporting.
+
+**Solutions:**
+
+1. **Verify installation:**
+ ```bash
+ npm list wdio-qase-reporter
+ ```
+
+2. **Check wdio.conf.js configuration:**
+ ```javascript
+ const WDIOQaseReporter = require('wdio-qase-reporter').default;
+
+ exports.config = {
+ reporters: [[WDIOQaseReporter, {}]],
+ };
+ ```
+
+3. **Ensure hooks are configured:**
+ ```javascript
+ const { beforeRunHook, afterRunHook } = require('wdio-qase-reporter');
+
+ exports.config = {
+ onPrepare: async function() {
+ await beforeRunHook();
+ },
+ onComplete: async function() {
+ await afterRunHook();
+ },
+ };
+ ```
+
+### Tests Not Appearing in Qase
+
+**Problem:** Tests run successfully but don't appear in Qase TestOps.
+
+**Solutions:**
+
+1. **Verify mode is set:**
+ ```bash
+ echo $QASE_MODE # Should output: testops
+ ```
+
+2. **Check configuration file:**
+ ```bash
+ cat qase.config.json
+ ```
+
+3. **Enable debug logging:**
+ ```json
+ {
+ "debug": true,
+ "mode": "TestOps"
+ }
+ ```
+
+4. **Verify hooks are called:**
+ - Check console output for "Qase" messages
+ - Ensure `beforeRunHook` and `afterRunHook` are configured
+
+### Steps Not Reporting
+
+**Problem:** `qase.step()` calls not appearing in Qase results.
+
+**Solutions:**
+
+1. **Ensure async/await is used:**
+ ```javascript
+ // Correct
+ await qase.step('Step name', async () => { ... });
+
+ // Incorrect
+ qase.step('Step name', () => { ... });
+ ```
+
+2. **Check if WebDriver steps are disabled:**
+ ```javascript
+ // If you want only custom steps
+ exports.config = {
+ reporters: [[WDIOQaseReporter, {
+ disableWebdriverStepsReporting: true,
+ }]],
+ };
+ ```
+
+### Cucumber Tags Not Working
+
+**Problem:** Cucumber `@QaseId` tags not linking to test cases.
+
+**Solutions:**
+
+1. **Verify `useCucumber` is enabled:**
+ ```javascript
+ exports.config = {
+ reporters: [[WDIOQaseReporter, {
+ useCucumber: true,
+ }]],
+ };
+ ```
+
+2. **Check tag syntax:**
+ ```gherkin
+ # Correct
+ @QaseId=1
+ Scenario: Test
+
+ # Incorrect
+ @QaseID=1
+ @qaseId=1
+ ```
+
+3. **Ensure framework is set to cucumber:**
+ ```javascript
+ exports.config = {
+ framework: 'cucumber',
+ };
+ ```
+
+### Attachments Not Uploading
+
+**Problem:** Screenshots or files not appearing in Qase.
+
+**Solutions:**
+
+1. **Verify file path exists:**
+ ```bash
+ ls -la /path/to/file
+ ```
+
+2. **Check attachment type:**
+ ```javascript
+ qase.attach({
+ name: 'screenshot.png',
+ content: screenshot,
+ type: 'image/png', // Must specify type for content
+ });
+ ```
+
+3. **Enable auto-screenshots:**
+ ```javascript
+ exports.config = {
+ reporters: [[WDIOQaseReporter, {
+ disableWebdriverScreenshotsReporting: false,
+ }]],
+ };
+ ```
+
+### Browser Launch Issues
+
+**Problem:** Browser fails to start or connection issues.
+
+**Solutions:**
+
+1. **Check browser driver installation:**
+ ```bash
+ npx wdio config # Re-run config wizard
+ ```
+
+2. **Verify browser is installed:**
+ ```bash
+ which google-chrome
+ which firefox
+ ```
+
+3. **Use headless mode:**
+ ```javascript
+ exports.config = {
+ capabilities: [{
+ browserName: 'chrome',
+ 'goog:chromeOptions': {
+ args: ['--headless', '--disable-gpu'],
+ },
+ }],
+ };
+ ```
+
+---
+
+## Integration Patterns
+
+### Pattern 1: Page Object Model with Qase
+
+```javascript
+// pageobjects/Login.page.js
+import Page from './Page';
+import { qase } from 'wdio-qase-reporter';
+
+class LoginPage extends Page {
+ get inputEmail() { return $('#email'); }
+ get inputPassword() { return $('#password'); }
+ get btnSubmit() { return $('#login-button'); }
+
+ async login(email, password) {
+ await qase.step('Enter email', async () => {
+ await this.inputEmail.setValue(email);
+ });
+
+ await qase.step('Enter password', async () => {
+ await this.inputPassword.setValue(password);
+ });
+
+ await qase.step('Click login button', async () => {
+ await this.btnSubmit.click();
+ });
+ }
+
+ open() {
+ return super.open('/login');
+ }
+}
+
+export default new LoginPage();
+
+// test/specs/login.spec.js
+import { qase } from 'wdio-qase-reporter';
+import LoginPage from '../pageobjects/Login.page';
+
+describe('Authentication', () => {
+ it(qase(1, 'User can log in'), async () => {
+ await LoginPage.open();
+ await LoginPage.login('user@example.com', 'password123');
+ await expect($('#dashboard')).toExist();
+ });
+});
+```
+
+### Pattern 2: Service-Based Architecture
+
+```javascript
+// services/api.service.js
+import { qase } from 'wdio-qase-reporter';
+
+class ApiService {
+ async makeRequest(endpoint, data) {
+ return await qase.step(\`API: \${endpoint}\`, async (step) => {
+ const response = await browser.call(async () => {
+ return await fetch(endpoint, {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+ });
+
+ step.attach({
+ name: 'api-response.json',
+ content: JSON.stringify(response, null, 2),
+ type: 'application/json',
+ });
+
+ return response;
+ });
+ }
+}
+
+export default new ApiService();
+```
+
+### Pattern 3: Hooks with Reporting
+
+```javascript
+// wdio.conf.js
+import { qase } from 'wdio-qase-reporter';
+
+exports.config = {
+ beforeTest: async function(test, context) {
+ await qase.step('Setup: Clear browser storage', async () => {
+ await browser.deleteAllCookies();
+ await browser.execute(() => localStorage.clear());
+ });
+ },
+
+ afterTest: async function(test, context, { passed }) {
+ if (!passed) {
+ await qase.step('Teardown: Capture failure screenshot', async (step) => {
+ const screenshot = await browser.takeScreenshot();
+ step.attach({
+ name: 'failure-screenshot.png',
+ content: screenshot,
+ type: 'image/png',
+ });
+ });
+ }
+ },
+};
+```
+
+---
+
+## Common Use Cases
+
+### Use Case 1: Basic Mocha Test with QaseID
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Smoke Tests', () => {
+ it(qase(101, 'Homepage loads'), async () => {
+ await browser.url('/');
+ await expect($('#header')).toExist();
+ });
+});
+```
+
+### Use Case 2: Cucumber Feature with Tags
+
+```gherkin
+Feature: Shopping Cart
+
+ @QaseId=201
+ Scenario: Add item to cart
+ Given I am on the product page
+ When I click add to cart
+ Then I should see the item in my cart
+```
+
+### Use Case 3: Visual Testing with Screenshots
+
+```javascript
+it(qase(301, 'Visual: Login page'), async () => {
+ await browser.url('/login');
+
+ const screenshot = await browser.takeScreenshot();
+
+ qase.attach({
+ name: 'login-page.png',
+ content: screenshot,
+ type: 'image/png',
+ });
+
+ await expect($('#login-form')).toExist();
+});
+```
+
+### Use Case 4: API Integration Test
+
+```javascript
+it(qase(401, 'API: Fetch user data'), async () => {
+ await browser.url('/dashboard');
+
+ const apiResponse = await browser.call(async () => {
+ return await fetch('https://api.example.com/user', {
+ headers: { Authorization: 'Bearer token' },
+ });
+ });
+
+ qase.attach({
+ name: 'api-response.json',
+ content: JSON.stringify(apiResponse, null, 2),
+ type: 'application/json',
+ });
+
+ await expect($('#user-name')).toHaveText('John Doe');
+});
+```
+
+### Use Case 5: Cross-Browser Testing
+
+Configure multiple browsers:
+
+```javascript
+// wdio.conf.js
+exports.config = {
+ capabilities: [
+ { browserName: 'chrome' },
+ { browserName: 'firefox' },
+ { browserName: 'safari' },
+ ],
+};
+
+// Test automatically runs in all browsers
+it(qase(501, 'Cross-browser test'), async () => {
+ qase.parameters({ browser: browser.capabilities.browserName });
+ await browser.url('/');
+ await expect($('#header')).toExist();
+});
+```
+
+### Use Case 6: Mobile Viewport Testing
+
+```javascript
+it(qase(601, 'Mobile: Responsive layout'), async () => {
+ await browser.setWindowSize(375, 667);
+
+ qase.parameters({
+ viewport: 'mobile',
+ width: 375,
+ height: 667,
+ });
+
+ await browser.url('/');
+ await expect($('#mobile-menu')).toExist();
+});
+```
+
+### Use Case 7: File Upload Testing
+
+```javascript
+it(qase(701, 'File upload'), async () => {
+ await browser.url('/upload');
+
+ await qase.step('Select file', async () => {
+ const filePath = require('path').join(__dirname, '../fixtures/test-file.pdf');
+ await $('#file-input').setValue(filePath);
+ });
+
+ await qase.step('Upload file', async () => {
+ await $('#upload-button').click();
+ });
+
+ await qase.step('Verify upload success', async () => {
+ await expect($('#upload-success')).toExist();
+ });
+});
+```
+
+### Use Case 8: Authentication Flow with Steps
+
+```javascript
+it(qase(801, 'Complete authentication flow'), async () => {
+ qase.fields({ severity: 'critical', priority: 'high' });
+
+ await qase.step('Navigate to login', async () => {
+ await browser.url('/login');
+ });
+
+ await qase.step('Enter credentials', async () => {
+ await $('#email').setValue('user@example.com');
+ await $('#password').setValue('password123');
+ });
+
+ await qase.step('Submit login', async () => {
+ await $('#login-button').click();
+ });
+
+ await qase.step('Verify dashboard', async () => {
+ await expect($('#dashboard')).toExist();
+ });
+});
+```
+
+### Use Case 9: Parallel Execution
+
+```javascript
+// wdio.conf.js
+exports.config = {
+ maxInstances: 5,
+ capabilities: [{
+ browserName: 'chrome',
+ maxInstances: 5,
+ }],
+};
+
+// Tests run in parallel automatically
+describe('Parallel Tests', () => {
+ it(qase(901, 'Test 1'), async () => {
+ await browser.url('/page1');
+ });
+
+ it(qase(902, 'Test 2'), async () => {
+ await browser.url('/page2');
+ });
+});
+```
+
+### Use Case 10: CI/CD Integration
+
+**.github/workflows/wdio.yml:**
+```yaml
+name: WDIO Tests
+
+on: [push, pull_request]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ with:
+ node-version: '18'
+ - run: npm ci
+ - run: npm test
+ env:
+ QASE_MODE: testops
+ QASE_TESTOPS_PROJECT: DEMO
+ QASE_TESTOPS_API_TOKEN: \${{ secrets.QASE_TOKEN }}
+```
+
+**package.json:**
+```json
+{
+ "scripts": {
+ "test": "wdio run wdio.conf.js"
+ }
+}
+```
+
+---
+
+## Complete Examples
+
+### Full Test Example (Mocha/Jasmine)
+
+```javascript
+import { qase } from 'wdio-qase-reporter';
+
+describe('Complete Example', () => {
+ it(qase([1, 2], 'Comprehensive test with all features'), async () => {
+ // Set metadata
+ qase.title('User can complete full registration flow');
+ qase.suite('Registration\tEnd-to-End');
+ qase.fields({
+ severity: 'critical',
+ priority: 'high',
+ layer: 'e2e',
+ description: 'Tests complete user registration flow from start to finish',
+ preconditions: 'Application is running and database is accessible',
+ });
+ qase.parameters({
+ Browser: 'Chrome',
+ Environment: 'staging',
+ });
+
+ // Execute test with steps
+ await qase.step('Navigate to registration page', async () => {
+ await browser.url('/register');
+ qase.attach({
+ name: 'page-load.txt',
+ content: 'Page loaded successfully',
+ contentType: 'text/plain',
+ });
+ });
+
+ await qase.step('Fill registration form', async () => {
+ await $('#username').setValue('testuser');
+ await $('#email').setValue('test@example.com');
+ await $('#password').setValue('SecurePass123!');
+ });
+
+ await qase.step('Submit form', async () => {
+ await $('button[type="submit"]').click();
+ await expect($('.success-message')).toBeDisplayed();
+ });
+
+ await qase.step('Verify email confirmation', async () => {
+ const message = await $('.email-sent').getText();
+ expect(message).toContain('Verification email sent');
+ });
+ });
+});
+```
+
+### Full Feature Example (Cucumber)
+
+```gherkin
+Feature: User Authentication
+ As a user
+ I want to log in to the application
+ So I can access my account
+
+ @QaseID=1
+ @QaseTitle=User can successfully log in with valid credentials
+ @QaseFields={"severity":"blocker","priority":"high","layer":"e2e"}
+ @QaseSuite=Authentication\tLogin\tHappy Path
+ Scenario: Successful login
+ Given I am on the login page
+ When I enter username "testuser@example.com"
+ And I enter password "SecurePass123!"
+ And I click the login button
+ Then I should be redirected to the dashboard
+ And I should see "Welcome back, Test User"
+```
+
+### Example Project Structure
+
+**Mocha/Jasmine:**
+```
+my-project/
+├── qase.config.json
+├── wdio.conf.js
+├── test/
+│ ├── specs/
+│ │ ├── auth.spec.js
+│ │ ├── checkout.spec.js
+│ │ └── ...
+│ └── pageobjects/
+│ ├── LoginPage.js
+│ ├── DashboardPage.js
+│ └── ...
+└── package.json
+```
+
+**Cucumber:**
+```
+my-project/
+├── qase.config.json
+├── wdio.conf.js
+├── features/
+│ ├── authentication.feature
+│ ├── checkout.feature
+│ └── ...
+├── step-definitions/
+│ ├── login.steps.js
+│ ├── checkout.steps.js
+│ └── ...
+└── package.json
+```
+
+---
+
+## See Also
+
+- [Configuration Reference](../../qase-javascript-commons/README.md)
+- [Multi-Project Support](MULTI_PROJECT.md)
+- [WebdriverIO Documentation](https://webdriver.io/)
+- [Example Tests](../../examples/multiProject/wdio/)