Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
e3145f7
feat(06-03): create TestCafe page objects and update config
gibiw Feb 16, 2026
7dd4805
feat(06-01): create page objects and update config for Playwright exa…
gibiw Feb 16, 2026
7847034
feat(06-04): create WDIO example directory structure with page object…
gibiw Feb 16, 2026
61363aa
feat(06-02): create page objects and update support files for Cypress…
gibiw Feb 16, 2026
8300525
feat(06-01): create test scenarios and update README for Playwright
gibiw Feb 16, 2026
5772a98
feat(06-03): create TestCafe e-commerce test scenarios
gibiw Feb 16, 2026
7897b3d
feat(06-04): create WDIO test scenarios and README
gibiw Feb 16, 2026
c8f7ce5
feat(06-02): create e-commerce test scenarios for Cypress example
gibiw Feb 16, 2026
4fddd88
chore(06-03): remove old TestCafe demo test files
gibiw Feb 16, 2026
1597857
chore(07-02): update Mocha configuration and delete old feature-demo …
gibiw Feb 16, 2026
c7e6984
chore(07-03): remove old Vitest feature-demo files and update config
gibiw Feb 16, 2026
87c344d
chore(07-01): prepare Jest examples for API testing scenarios
gibiw Feb 16, 2026
acf3ce2
feat(07-02): create realistic API test scenarios for Mocha
gibiw Feb 16, 2026
7bf9627
chore(07-03): remove old Vitest feature-demo files and update config
gibiw Feb 16, 2026
8f4825c
feat(07-03): add realistic Vitest API testing examples with JSONPlace…
gibiw Feb 16, 2026
3b173ae
feat(07-01): add realistic Jest API testing examples with JSONPlaceho…
gibiw Feb 16, 2026
03f95a4
Merge branch 'feat/07-01-jest-api-examples-clean' into feat/v1.1-real…
gibiw Feb 16, 2026
8c9138c
Merge branch 'feat/06-02-cypress-e2e-examples' into feat/v1.1-realist…
gibiw Feb 16, 2026
4968014
docs(08): create phase 8 plans for BDD and collection examples
gibiw Feb 16, 2026
d20c2bb
chore(08-01): update CucumberJS config and remove old synthetic examples
gibiw Feb 16, 2026
376b028
chore(08-02): update Newman config and remove old synthetic collection
gibiw Feb 16, 2026
6786940
feat(08-01): add realistic CucumberJS BDD API test examples
gibiw Feb 16, 2026
40378ff
feat(08-02): add realistic Newman collection with JSONPlaceholder API…
gibiw Feb 16, 2026
cc527f6
docs(08-01): complete CucumberJS BDD example plan
gibiw Feb 16, 2026
e545fd1
docs(08-02): complete Newman collection example plan
gibiw Feb 16, 2026
1e6be64
Updated tests
gibiw Feb 16, 2026
c9e8310
docs(09): create phase 9 integration validation plans
gibiw Feb 16, 2026
2cd6e9e
fix(09): revise plans based on checker feedback
gibiw Feb 16, 2026
ec1530e
chore(09-01): add QASE_MODE environment fallback to example test scripts
gibiw Feb 16, 2026
99600b5
docs(09-01): standardize README structure across all 9 examples
gibiw Feb 16, 2026
7553cbc
docs(09-01): complete self-containment and documentation standardizat…
gibiw Feb 16, 2026
84a5ac3
feat(09-02): create validation script with feature coverage and minim…
gibiw Feb 16, 2026
a9b4517
feat(09-02): create GitHub Actions workflow for example validation
gibiw Feb 16, 2026
45617cd
docs(09-02): complete validation tooling and CI/CD workflow plan
gibiw Feb 16, 2026
5249b39
docs(phase-09): complete phase execution and verification
gibiw Feb 16, 2026
11a2cc2
feat: add suite support to TestCafe and comment support to WDIO
gibiw Feb 16, 2026
b53f1fe
feat: update multi-project examples with realistic test scenarios
gibiw Feb 16, 2026
6bd34cd
chore: remove .planning directory from tracked files
gibiw Feb 16, 2026
0b5a204
fix(ci): fix Cypress title validation and Ubuntu Noble dependencies
gibiw Feb 16, 2026
6174747
chore: remove example validation workflow and script
gibiw Feb 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 38 additions & 9 deletions examples/multiProject/cucumberjs/features/multiProject.feature
Original file line number Diff line number Diff line change
@@ -1,14 +1,43 @@
Feature: Multi-project example
Scenarios for testops_multi mode. Use @qaseid.PROJ(ids) tags for multi-project.
Feature: Multi-project API - User Operations
API CRUD operations reported to multiple Qase projects.
Use @qaseid.PROJ(ids) tags for multi-project reporting.

Scenario: Scenario without Qase ID
Given I have a step
Background:
Given the API is available at "https://jsonplaceholder.typicode.com"

@qaseid.PROJ1(1)
@qaseid.PROJ2(2)
Scenario: Scenario reported to two projects
Given I have a step
Scenario: Get all users returns 10 users
When I send a GET request to "/users"
Then the response status should be 200
And the response should contain 10 items
And each item should have an "id" field
And each item should have an "email" field

@QaseID=3
Scenario: Scenario with legacy single-project tag
Given I have a step
@qaseid.PROJ1(3)
@qaseid.PROJ2(4)
Scenario: Get single user by ID returns correct user
When I send a GET request to "/users/1"
Then the response status should be 200
And the response "name" should be "Leanne Graham"
And the response "email" should be "Sincere@april.biz"

@qaseid.PROJ1(5)
@qaseid.PROJ2(6)
Scenario: Create new user returns 201 with ID
When I send a POST request to "/users" with body:
"""
{
"name": "Test User",
"username": "testuser",
"email": "test@example.com"
}
"""
Then the response status should be 201
And the response should have an "id" field

@qaseid.PROJ1(7)
@qaseid.PROJ2(8)
Scenario: Delete user returns 200 status
When I send a DELETE request to "/users/1"
Then the response status should be 200
79 changes: 75 additions & 4 deletions examples/multiProject/cucumberjs/step_definitions/steps.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,77 @@
const { Given } = require('@cucumber/cucumber');
const { Given, When, Then } = require('@cucumber/cucumber');
const assert = require('assert');

Given('I have a step', function () {
console.log('I have a step');
this.attach("I'm an attachment", 'text/plain');
Given('the API is available at {string}', function (baseUrl) {
this.baseUrl = baseUrl;
});

When('I send a GET request to {string}', async function (endpoint) {
const url = `${this.baseUrl}${endpoint}`;
this.response = await fetch(url);
this.responseData = await this.response.json();

this.attach(JSON.stringify({
url: url,
status: this.response.status,
body: Array.isArray(this.responseData)
? `[${this.responseData.length} items]`
: this.responseData,
}, null, 2), 'application/json');
});

When('I send a POST request to {string} with body:', async function (endpoint, docString) {
const url = `${this.baseUrl}${endpoint}`;
this.response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: docString,
});
this.responseData = await this.response.json();

this.attach(JSON.stringify({
request: { url, method: 'POST', body: JSON.parse(docString) },
response: { status: this.response.status, body: this.responseData },
}, null, 2), 'application/json');
});

When('I send a DELETE request to {string}', async function (endpoint) {
const url = `${this.baseUrl}${endpoint}`;
this.response = await fetch(url, { method: 'DELETE' });
this.responseData = await this.response.json();

this.attach(JSON.stringify({
url: url,
method: 'DELETE',
status: this.response.status,
}, null, 2), 'application/json');
});

Then('the response status should be {int}', function (expectedStatus) {
assert.strictEqual(this.response.status, expectedStatus,
`Expected status ${expectedStatus} but got ${this.response.status}`);
});

Then('the response should contain {int} items', function (count) {
assert.ok(Array.isArray(this.responseData),
`Expected response to be an array but got ${typeof this.responseData}`);
assert.strictEqual(this.responseData.length, count,
`Expected ${count} items but got ${this.responseData.length}`);
});

Then('each item should have an {string} field', function (fieldName) {
assert.ok(Array.isArray(this.responseData), 'Response should be an array');
for (const item of this.responseData) {
assert.ok(item[fieldName] !== undefined,
`Item missing required field: ${fieldName}`);
}
});

Then('the response {string} should be {string}', function (field, expected) {
assert.strictEqual(String(this.responseData[field]), expected,
`Expected ${field} to be "${expected}" but got "${this.responseData[field]}"`);
});

Then('the response should have an {string} field', function (fieldName) {
assert.ok(this.responseData[fieldName] !== undefined,
`Response missing required field: ${fieldName}`);
});
154 changes: 140 additions & 14 deletions examples/multiProject/cypress/cypress/e2e/multiProject.cy.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,145 @@
const { qase } = require('cypress-qase-reporter/mocha');

describe('Multi-project example', () => {
// Map this test to case 1 in PROJ1 and case 2 in PROJ2. Replace IDs with real case IDs in your projects.
qase.projects({ PROJ1: [1], PROJ2: [2] }, it('A test reported to two projects', () => {
cy.visit('https://example.cypress.io');
cy.contains('type').click();
cy.url().should('include', '/commands/actions');
}));

qase.projects(
{ PROJ1: [10, 11], PROJ2: [20] },
it('Another test with multiple cases per project', () => {
cy.visit('https://example.cypress.io');
cy.contains('type').click();
cy.url().should('include', '/commands/actions');
describe('Multi-project Login Scenarios', () => {
beforeEach(() => {
cy.visit('https://www.saucedemo.com');
});

// Report to PROJ1 (case 1) and PROJ2 (case 2)
qase.projects({ PROJ1: [1], PROJ2: [2] },
it('User can login with valid credentials', () => {
qase.fields({ severity: 'critical', priority: 'high', layer: 'e2e' });
qase.suite('E-commerce\tAuthentication\tLogin');

qase.step('Fill in credentials and submit', () => {
cy.get('[data-test="username"]').type('standard_user');
cy.get('[data-test="password"]').type('secret_sauce');
cy.get('[data-test="login-button"]').click();
});

qase.step('Verify successful login', () => {
cy.url().should('include', '/inventory.html');
cy.get('[data-test="title"]').should('have.text', 'Products');
});

qase.comment('Login successful — reported to PROJ1 and PROJ2');
}),
);

// Report to PROJ1 (case 3) and PROJ2 (case 4)
qase.projects({ PROJ1: [3], PROJ2: [4] },
it('Invalid password shows error', () => {
qase.fields({ severity: 'high', priority: 'high', layer: 'e2e' });
qase.suite('E-commerce\tAuthentication\tLogin');
qase.parameters({ username: 'standard_user', password: 'wrong_password' });

qase.step('Attempt login with invalid credentials', () => {
cy.get('[data-test="username"]').type('standard_user');
cy.get('[data-test="password"]').type('wrong_password');
cy.get('[data-test="login-button"]').click();
});

qase.step('Verify error message', () => {
cy.get('[data-test="error"]')
.should('be.visible')
.and('contain.text', 'Username and password do not match');
});

qase.comment('Error correctly displayed — tracked in both projects');
}),
);

// Report to PROJ1 (case 5) and PROJ2 (case 6)
qase.projects({ PROJ1: [5], PROJ2: [6] },
it('Locked user cannot login', () => {
qase.fields({ severity: 'high', priority: 'medium', layer: 'e2e' });
qase.suite('E-commerce\tAuthentication\tLogin');
qase.parameters({ username: 'locked_out_user' });

qase.step('Attempt login with locked user', () => {
cy.get('[data-test="username"]').type('locked_out_user');
cy.get('[data-test="password"]').type('secret_sauce');
cy.get('[data-test="login-button"]').click();
});

qase.step('Verify locked-out error', () => {
cy.get('[data-test="error"]')
.should('be.visible')
.and('contain.text', 'Sorry, this user has been locked out');
});
}),
);
});

describe('Multi-project Cart Scenarios', () => {
beforeEach(() => {
cy.visit('https://www.saucedemo.com');
cy.get('[data-test="username"]').type('standard_user');
cy.get('[data-test="password"]').type('secret_sauce');
cy.get('[data-test="login-button"]').click();
cy.url().should('include', '/inventory.html');
});

// Report to PROJ1 (cases 7, 8) and PROJ2 (case 9)
qase.projects({ PROJ1: [7, 8], PROJ2: [9] },
it('Add product to cart', () => {
qase.fields({ severity: 'critical', priority: 'high', layer: 'e2e' });
qase.suite('E-commerce\tCart\tAdd Items');
qase.parameters({ product: 'Sauce Labs Backpack' });

qase.step('Add Sauce Labs Backpack to cart', () => {
cy.get('[data-test="add-to-cart-sauce-labs-backpack"]').click();
});

qase.step('Verify cart badge shows 1 item', () => {
cy.get('.shopping_cart_badge').should('have.text', '1');
});

qase.step('Navigate to cart and verify product', () => {
cy.get('.shopping_cart_link').click();
cy.get('.inventory_item_name').should('contain.text', 'Backpack');
});

qase.comment('Product added to cart — reported to both projects');

qase.attach({
name: 'cart-state.json',
content: JSON.stringify({
product: 'Sauce Labs Backpack',
quantity: 1,
projects: ['PROJ1', 'PROJ2'],
}, null, 2),
contentType: 'application/json',
});
}),
);

// Report to PROJ1 (case 10) and PROJ2 (case 11)
qase.projects({ PROJ1: [10], PROJ2: [11] },
it('Complete checkout flow', () => {
qase.fields({ severity: 'critical', priority: 'critical', layer: 'e2e' });
qase.suite('E-commerce\tCheckout\tComplete');
qase.parameters({ firstName: 'John', lastName: 'Doe', postalCode: '12345' });

qase.step('Add product and go to checkout', () => {
cy.get('[data-test="add-to-cart-sauce-labs-backpack"]').click();
cy.get('.shopping_cart_link').click();
cy.get('[data-test="checkout"]').click();
});

qase.step('Fill checkout information', () => {
cy.get('[data-test="firstName"]').type('John');
cy.get('[data-test="lastName"]').type('Doe');
cy.get('[data-test="postalCode"]').type('12345');
});

qase.step('Complete and verify order', () => {
cy.get('[data-test="continue"]').click();
cy.get('[data-test="finish"]').click();
cy.get('.complete-header').should('contain.text', 'Thank you');
});

qase.comment('Full checkout flow verified across both projects');
}),
);
});
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
Feature: Multi-project tests with @badeball/cypress-cucumber-preprocessor

# Multi-project: case 1 in PROJ1, case 2 in PROJ2. Use @qaseid.PROJECT(ids) format.
@qaseid.PROJ1(1)
@qaseid.PROJ2(2)
Scenario: test reported to two projects
Scenario: Homepage navigation reported to two projects
Given I am on the homepage
When I click on the first link
Then I should see the first link

@qaseid.PROJ1(10,11)
@qaseid.PROJ2(20)
Scenario: test with multiple cases per project
@qaseid.PROJ1(3)
@qaseid.PROJ2(4)
Scenario: Homepage navigation with expected failure
Given I am on the homepage
When I should see the first link failed
Then I should see the first link

@qaseid.PROJ1(5,6)
@qaseid.PROJ2(7)
Scenario: Multiple case IDs across projects
Given I am on the homepage
When I click on the first link
Then I should see the first link
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@ Feature: Multi-project tests with cypress-cucumber-preprocessor

@qaseid.PROJ1(1)
@qaseid.PROJ2(2)
Scenario: test reported to two projects
Scenario: Homepage navigation reported to two projects
Given I am on the homepage
When I click on the first link
Then I should see the first link

@qaseid.PROJ1(10,11)
@qaseid.PROJ2(20)
Scenario: test with multiple cases per project
@qaseid.PROJ1(3)
@qaseid.PROJ2(4)
Scenario: Homepage navigation with expected failure
Given I am on the homepage
When I should see the first link failed
Then I should see the first link

@qaseid.PROJ1(5,6)
@qaseid.PROJ2(7)
Scenario: Multiple case IDs across projects
Given I am on the homepage
When I click on the first link
Then I should see the first link
Loading
Loading