Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
46 changes: 46 additions & 0 deletions example/mocha/cypress_tags_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/// <reference types="Cypress" />

const TestTypes = {
regression: '@regression',
smoke: '@smoke',
};

const Services = {
integrations: 'integrations',
};

describe(
'Ability to Edit and Delete Assets',
{
tags: [TestTypes.regression, Services.integrations, '@multiTenant', 'v8'],
},
() => {
beforeEach(() => {
cy.visit('http://localhost:8080/assets');
});

it('edits an asset', () => {
cy.get('.edit').click();
});

it('deletes an asset', { tags: ['@slow', `nightly`] }, () => {
cy.get('.delete').click();
});

describe('nested suite', { tags: 'wip' }, () => {
it('inherits tags from all parent suites', () => {
cy.get('.nested').click();
});
});
},
);

describe('Suite without tags', () => {
it('has no tags', () => {
cy.visit('http://localhost:8080');
});

it.skip('skipped test with own tags', { tags: '@quarantine' }, () => {
cy.visit('http://localhost:8080');
});
});
6 changes: 6 additions & 0 deletions src/lib/frameworks/mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const {
getLineNumber,
getEndLineNumber,
getCode,
cypress,
getAllSuiteTags,
} = require('../utils');

module.exports = (ast, file = '', source = '', opts = {}) => {
Expand All @@ -23,6 +25,7 @@ module.exports = (ast, file = '', source = '', opts = {}) => {

function addSuite(path) {
currentSuite = currentSuite.filter(s => s.loc.end.line > path.loc.start.line);
path.tags = cypress.getTags(path);
currentSuite.push(path);
}

Expand Down Expand Up @@ -94,6 +97,7 @@ module.exports = (ast, file = '', source = '', opts = {}) => {
line: getLineNumber(path),
code: getCode(source, getLineNumber(path), getEndLineNumber(path), isLineNumber),
file,
tags: [...getAllSuiteTags(currentSuite), ...cypress.getTags(path.parentPath.container)],
skipped: true,
});
}
Expand All @@ -117,6 +121,7 @@ module.exports = (ast, file = '', source = '', opts = {}) => {
updatePoint: getUpdatePoint(path.parent),
line: getLineNumber(path),
code: getCode(source, getLineNumber(path), getEndLineNumber(path), isLineNumber),
tags: [...getAllSuiteTags(currentSuite), ...cypress.getTags(path.parent)],
skipped: true,
file,
});
Expand Down Expand Up @@ -147,6 +152,7 @@ module.exports = (ast, file = '', source = '', opts = {}) => {
line: getLineNumber(path),
code,
file,
tags: [...getAllSuiteTags(currentSuite), ...cypress.getTags(path.parent)],
skipped: !!currentSuite.filter(s => s.skipped).length,
});
}
Expand Down
33 changes: 33 additions & 0 deletions src/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,38 @@ const playwright = {
},
};

const cypress = {
// extracts tags from Cypress test config object: describe('title', { tags: [...] }, fn) or it('title', { tags: '@smoke' }, fn)
getTags: node => {
const args = node?.arguments;
if (!args?.length) return [];
const configArg = args.find(arg => arg.type === 'ObjectExpression');
if (!configArg) return [];

const tagsProp = configArg.properties.find(prop => prop.key?.name === 'tags' || prop.key?.value === 'tags');
if (!tagsProp || !tagsProp.value) return [];

// tags value could be a single tag or an array of tags
const elements = tagsProp.value.type === 'ArrayExpression' ? tagsProp.value.elements : [tagsProp.value];

const getTagName = el => {
if (!el) return;
if (el.type === 'StringLiteral' || el.type === 'Literal') return el.value;
if (el.type === 'TemplateLiteral' && !el.expressions.length && el.quasis.length === 1) {
return el.quasis[0].value.cooked;
}
// enum-style tags like TestTypes.regression can't be resolved statically; use the property name
if (el.type === 'MemberExpression' && el.property?.type === 'Identifier') return el.property.name;
if (el.type === 'Identifier') return el.name;
};

return elements
.map(getTagName)
.filter(tag => typeof tag === 'string' && tag.length)
.map(tag => (tag.startsWith('@') ? tag.substring(1) : tag));
},
};

const arrayCompare = function (a, b, id) {
const missing = [];
const found = [];
Expand Down Expand Up @@ -300,6 +332,7 @@ module.exports = {
replaceAtPoint,
cleanAtPoint,
playwright,
cypress,
arrayCompare,
getAllSuiteTags,
formatErrorMessage,
Expand Down
36 changes: 36 additions & 0 deletions tests/mocha_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,42 @@ describe('mocha parser', () => {
});
});

context('cypress tags', () => {
let tests;

before(() => {
source = fs.readFileSync('./example/mocha/cypress_tags_spec.js').toString();
ast = parser.parse(source);
tests = mochaParser(ast, '', source);
});

it('should sync tags from describe config to tests', () => {
const test = tests.find(t => t.name === 'edits an asset');
expect(test.tags).to.eql(['regression', 'integrations', 'multiTenant', 'v8']);
});

it('should combine suite tags with test tags', () => {
const test = tests.find(t => t.name === 'deletes an asset');
expect(test.tags).to.eql(['regression', 'integrations', 'multiTenant', 'v8', 'slow', 'nightly']);
});

it('should inherit tags from all parent suites', () => {
const test = tests.find(t => t.name === 'inherits tags from all parent suites');
expect(test.tags).to.eql(['regression', 'integrations', 'multiTenant', 'v8', 'wip']);
});

it('should not add tags to tests without them', () => {
const test = tests.find(t => t.name === 'has no tags');
expect(test.tags).to.eql([]);
});

it('should parse tags of skipped tests', () => {
const test = tests.find(t => t.name === 'skipped test with own tags');
expect(test.skipped).to.eql(true);
expect(test.tags).to.eql(['quarantine']);
});
});

context('graphql tests', () => {
before(() => {
source = fs.readFileSync('./example/mocha/graphql_test.js').toString();
Expand Down
Loading