Skip to content

Commit

Permalink
Merge branch 'master' of github.com:Codeception/CodeceptJS
Browse files Browse the repository at this point in the history
  • Loading branch information
DavertMik committed May 14, 2020
2 parents 086aecb + 806537a commit adfab76
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 47 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2.6.5

* Added `test.skipped` event to run-workers, fixing allure reports with skipped tests in workers #2391. Fix #2387 by @koushikmohan1996
* [Playwright] Fixed calling `waitFor*` methods with custom locators #2314. Fix #2389 by @Georgegriff

## 2.6.4

* [Playwright] **Playwright 1.0 support** by @Georgegriff.
Expand Down
8 changes: 7 additions & 1 deletion lib/command/run-workers.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const stats = {
passes: 0,
failures: 0,
tests: 0,
pending: 0,
};


Expand Down Expand Up @@ -108,6 +109,10 @@ module.exports = function (workers, options) {
output.test.passed(repackTest(message.data));
updateFinishedTests(repackTest(message.data), maxReruns);
break;
case event.test.skipped:
output.test.skipped(repackTest(message.data));
updateFinishedTests(repackTest(message.data), maxReruns);
break;
case event.suite.before: output.suite.started(message.data); break;
case event.all.after: appendStats(message.data); break;
}
Expand Down Expand Up @@ -137,7 +142,7 @@ function printResults() {
stats.duration = stats.end - stats.start;
output.print();
if (stats.tests === 0 || (stats.passes && !errors.length)) {
output.result(stats.passes, stats.failures, 0, `${stats.duration / 1000}s`);
output.result(stats.passes, stats.failures, stats.pending, `${stats.duration / 1000}s`);
}
if (stats.failures) {
output.print();
Expand Down Expand Up @@ -206,6 +211,7 @@ function appendStats(newStats) {
stats.passes += newStats.passes;
stats.failures += newStats.failures;
stats.tests += newStats.tests;
stats.pending += newStats.pending;
}

function repackTest(test) {
Expand Down
5 changes: 5 additions & 0 deletions lib/command/workers/runTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ function initializeListeners() {
event.dispatcher.on(event.test.failed, test => sendToParentThread({ event: event.test.failed, workerIndex, data: simplifyTest(test) }));
event.dispatcher.on(event.test.passed, test => sendToParentThread({ event: event.test.passed, workerIndex, data: simplifyTest(test) }));
event.dispatcher.on(event.test.started, test => sendToParentThread({ event: event.test.started, workerIndex, data: simplifyTest(test) }));
event.dispatcher.on(event.test.skipped, test => sendToParentThread({ event: event.test.skipped, workerIndex, data: simplifyTest(test) }));

event.dispatcher.on(event.hook.failed, (test, err) => sendToParentThread({ event: event.hook.failed, workerIndex, data: simplifyTest(test, err) }));
event.dispatcher.on(event.hook.passed, (test, err) => sendToParentThread({ event: event.hook.passed, workerIndex, data: simplifyTest(test, err) }));
Expand All @@ -122,13 +123,17 @@ function collectStats() {
passes: 0,
failures: 0,
tests: 0,
pending: 0,
};
event.dispatcher.on(event.test.passed, () => {
stats.passes++;
});
event.dispatcher.on(event.test.failed, () => {
stats.failures++;
});
event.dispatcher.on(event.test.skipped, () => {
stats.pending++;
});
event.dispatcher.on(event.test.finished, () => {
stats.tests++;
});
Expand Down
55 changes: 20 additions & 35 deletions lib/helper/Playwright.js
Original file line number Diff line number Diff line change
Expand Up @@ -1675,19 +1675,9 @@ class Playwright extends Helper {
async waitForEnabled(locator, sec) {
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
locator = new Locator(locator, 'css');
const matcher = await this.context;
let waiter;
const context = await this._getContext();
if (!locator.isXPath()) {
// playwright combined selectors
waiter = context.waitForSelector(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()} >> __disabled=false`, { timeout: waitTimeout });
} else {
const enabledFn = function ([locator, $XPath]) {
eval($XPath); // eslint-disable-line no-eval
return $XPath(null, locator).filter(el => !el.disabled).length > 0;
};
waiter = context.waitForFunction(enabledFn, [locator.value, $XPath.toString()], { timeout: waitTimeout });
}
// playwright combined selectors
const waiter = context.waitForSelector(`${buildLocatorString(locator)} >> __disabled=false`, { timeout: waitTimeout });
return waiter.catch((err) => {
throw new Error(`element (${locator.toString()}) still not enabled after ${waitTimeout / 1000} sec\n${err.message}`);
});
Expand All @@ -1699,19 +1689,9 @@ class Playwright extends Helper {
async waitForValue(field, value, sec) {
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
const locator = new Locator(field, 'css');
const matcher = await this.context;
let waiter;
const context = await this._getContext();
if (!locator.isXPath()) {
// uses a custom selector engine for finding value properties on elements
waiter = context.waitForSelector(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()} >> __value=${value}`, { timeout: waitTimeout, state: 'visible' });
} else {
const valueFn = function ([locator, $XPath, value]) {
eval($XPath); // eslint-disable-line no-eval
return $XPath(null, locator).filter(el => (el.value || '').indexOf(value) !== -1).length > 0;
};
waiter = context.waitForFunction(valueFn, [locator.value, $XPath.toString(), value], { timeout: waitTimeout });
}
// uses a custom selector engine for finding value properties on elements
const waiter = context.waitForSelector(`${buildLocatorString(locator)} >> __value=${value}`, { timeout: waitTimeout, state: 'visible' });
return waiter.catch((err) => {
const loc = locator.toString();
throw new Error(`element (${loc}) is not in DOM or there is no element(${loc}) with value "${value}" after ${waitTimeout / 1000} sec\n${err.message}`);
Expand Down Expand Up @@ -1753,7 +1733,7 @@ class Playwright extends Helper {
* {{> waitForClickable }}
*/
async waitForClickable(locator, waitTimeout) {
console.log('I.waitForClickable is DEPRECATED: This is no longer needed, Playwright automatically waits for element to be clikable');
console.log('I.waitForClickable is DEPRECATED: This is no longer needed, Playwright automatically waits for element to be clickable');
console.log('Remove usage of this function');
}

Expand All @@ -1766,7 +1746,7 @@ class Playwright extends Helper {
locator = new Locator(locator, 'css');

const context = await this._getContext();
const waiter = context.waitForSelector(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()}`, { timeout: waitTimeout, state: 'attached' });
const waiter = context.waitForSelector(buildLocatorString(locator), { timeout: waitTimeout, state: 'attached' });
return waiter.catch((err) => {
throw new Error(`element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${err.message}`);
});
Expand All @@ -1781,7 +1761,7 @@ class Playwright extends Helper {
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
locator = new Locator(locator, 'css');
const context = await this._getContext();
const waiter = context.waitForSelector(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()}`, { timeout: waitTimeout, state: 'visible' });
const waiter = context.waitForSelector(buildLocatorString(locator), { timeout: waitTimeout, state: 'visible' });
return waiter.catch((err) => {
throw new Error(`element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec\n${err.message}`);
});
Expand All @@ -1794,7 +1774,7 @@ class Playwright extends Helper {
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
locator = new Locator(locator, 'css');
const context = await this._getContext();
const waiter = context.waitForSelector(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()}`, { timeout: waitTimeout, state: 'hidden' });
const waiter = context.waitForSelector(buildLocatorString(locator), { timeout: waitTimeout, state: 'hidden' });
return waiter.catch((err) => {
throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec\n${err.message}`);
});
Expand All @@ -1807,7 +1787,7 @@ class Playwright extends Helper {
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
locator = new Locator(locator, 'css');
const context = await this._getContext();
return context.waitForSelector(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()}`, { timeout: waitTimeout, state: 'hidden' }).catch((err) => {
return context.waitForSelector(buildLocatorString(locator), { timeout: waitTimeout, state: 'hidden' }).catch((err) => {
throw new Error(`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`);
});
}
Expand Down Expand Up @@ -2061,14 +2041,19 @@ class Playwright extends Helper {

module.exports = Playwright;

async function findElements(matcher, locator) {
locator = new Locator(locator, 'css');
function buildLocatorString(locator) {
if (locator.isCustom()) {
return matcher.$$(`${locator.type}=${locator.value}`);
} if (!locator.isXPath()) {
return matcher.$$(locator.simplify());
return `${locator.type}=${locator.value}`;
} if (locator.isXPath()) {
// dont rely on heuristics of playwright for figuring out xpath
return `xpath=${locator.value}`;
}
return matcher.$$(`xpath=${locator.value}`);
return locator.simplify();
}

async function findElements(matcher, locator) {
locator = new Locator(locator, 'css');
return matcher.$$(buildLocatorString(locator));
}

async function proceedClick(locator, context = null, options = {}) {
Expand Down
3 changes: 3 additions & 0 deletions lib/reporter/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ class Cli extends Base {
for (const test of suite.tests) {
if (!test.state && !this.loadedTests.includes(test.id)) {
if (matchTest(grep, test.title)) {
if (!test.opts) {
test.opts = {};
}
if (!test.opts.skipInfo) {
test.opts.skipInfo = {};
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codeceptjs",
"version": "2.6.4",
"version": "2.6.5",
"description": "Supercharged End 2 End Testing Framework for NodeJS",
"keywords": [
"acceptance",
Expand Down
56 changes: 56 additions & 0 deletions test/data/app/view/form/custom_locator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<html>
<head>
<style>
.invisible_button { display: none; }
</style>
</head>

<body>

<div data-test-id="step_1" class="invisible_button">Step One Button</div>
<div data-test-id="step_2" class="invisible_button">Step Two Button</div>
<div data-test-id="step_3" class="invisible_button">Step Three Button</div>
<div data-test-id="step_4" class="invisible_button">Steps Complete!</div>

<script>
/**
* Utility Functions
*/
function _prepareStepButtons() {
['step_1', 'step_2', 'step_3'].forEach( function( id, index ) {
var num = index + 2,
nextIDNum = num.toString();
getByAttribute( id ).addEventListener( 'click', function( event ) {
var nextID = 'step_' + nextIDNum;
removeClass( getByAttribute( nextID ), 'invisible_button' );
});
});
}
function getByAttribute( id ) {
return document.querySelector( `[data-test-id="${id}"]` );
}
function removeClass( el, cls ) {
el.classList.remove( cls );
return el;
}
/**
* Do Stuff
*/
_prepareStepButtons();
setTimeout(function () {
removeClass( getByAttribute('step_1'), 'invisible_button' );
}, 1000);
</script>

</body>
</html>
59 changes: 59 additions & 0 deletions test/helper/webapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ const formContents = require('../../lib/utils').test.submittedData(dataFile);
const fileExists = require('../../lib/utils').fileExists;
const secret = require('../../lib/secret').secret;

const Locator = require('../../lib/locator');
const customLocators = require('../../lib/plugin/customLocator');

let originalLocators;
let I;
let data;
let siteUrl;
Expand Down Expand Up @@ -1320,4 +1324,59 @@ module.exports.tests = function () {
});
});
});

describe('#customLocators', () => {
beforeEach(() => {
originalLocators = Locator.filters;
Locator.filters = [];
});
afterEach(() => {
// reset custom locators
Locator.filters = originalLocators;
});
it('should support xpath custom locator by default', async () => {
customLocators({
attribute: 'data-test-id',
enabled: true,
});
await I.amOnPage('/form/custom_locator');
await I.dontSee('Step One Button');
await I.dontSeeElement('$step_1');
await I.waitForVisible('$step_1', 2);
await I.seeElement('$step_1');
await I.click('$step_1');
await I.waitForVisible('$step_2', 2);
await I.see('Step Two Button');
});
it('can use css strategy for custom locator', async () => {
customLocators({
attribute: 'data-test-id',
enabled: true,
strategy: 'css',
});
await I.amOnPage('/form/custom_locator');
await I.dontSee('Step One Button');
await I.dontSeeElement('$step_1');
await I.waitForVisible('$step_1', 2);
await I.seeElement('$step_1');
await I.click('$step_1');
await I.waitForVisible('$step_2', 2);
await I.see('Step Two Button');
});
it('can use xpath strategy for custom locator', async () => {
customLocators({
attribute: 'data-test-id',
enabled: true,
strategy: 'xpath',
});
await I.amOnPage('/form/custom_locator');
await I.dontSee('Step One Button');
await I.dontSeeElement('$step_1');
await I.waitForVisible('$step_1', 2);
await I.seeElement('$step_1');
await I.click('$step_1');
await I.waitForVisible('$step_2', 2);
await I.see('Step Two Button');
});
});
};
26 changes: 16 additions & 10 deletions test/runner/allure_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,14 @@ describe('CodeceptJS Allure Plugin', () => {
stdout.should.include('FAIL | 0 passed, 1 failed');

const files = fs.readdirSync(path.join(codecept_dir, 'output/failed'));
const testResultPath = files[0];
assert(testResultPath.match(/\.xml$/), 'not a xml file');
const file = fs.readFileSync(path.join(codecept_dir, 'output/failed', testResultPath), 'utf8');
file.should.include('BeforeSuite of suite failing setup test suite: failed.');
file.should.include('the before suite setup failed');
// join all reports together
const reports = files.map((testResultPath) => {
assert(testResultPath.match(/\.xml$/), 'not a xml file');
return fs.readFileSync(path.join(codecept_dir, 'output/failed', testResultPath), 'utf8');
}).join(' ');
reports.should.include('BeforeSuite of suite failing setup test suite: failed.');
reports.should.include('the before suite setup failed');
reports.should.include('Skipped due to failure in \'before\' hook');
done();
});
});
Expand All @@ -68,11 +71,14 @@ describe('CodeceptJS Allure Plugin', () => {
stdout.should.include('FAIL | 0 passed');

const files = fs.readdirSync(path.join(codecept_dir, 'output/failed'));
const testResultPath = files[0];
assert(testResultPath.match(/\.xml$/), 'not a xml file');
const file = fs.readFileSync(path.join(codecept_dir, 'output/failed', testResultPath), 'utf8');
file.should.include('BeforeSuite of suite failing setup test suite: failed.');
file.should.include('the before suite setup failed');
const reports = files.map((testResultPath) => {
assert(testResultPath.match(/\.xml$/), 'not a xml file');
return fs.readFileSync(path.join(codecept_dir, 'output/failed', testResultPath), 'utf8');
}).join(' ');
reports.should.include('BeforeSuite of suite failing setup test suite: failed.');
reports.should.include('the before suite setup failed');
// the line below does not work in workers needs investigating https://github.com/Codeception/CodeceptJS/issues/2391
// reports.should.include('Skipped due to failure in \'before\' hook');
done();
});
});
Expand Down

0 comments on commit adfab76

Please sign in to comment.