From 35f6f3b58d2ca1d1834faf53649e99e8b3c9e50e Mon Sep 17 00:00:00 2001 From: George Griffiths Date: Sat, 9 May 2020 11:25:47 +0100 Subject: [PATCH 1/7] Makes suggested changes to have Chromium install --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index a8dedf95e..05dee4522 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,6 +19,7 @@ RUN apt-get update && apt-get install -y wget --no-install-recommends \ && apt-get update \ && apt-get install -y google-chrome-unstable \ --no-install-recommends \ + && apt-get install -y libgbm1 \ && rm -rf /var/lib/apt/lists/* \ && apt-get purge --auto-remove -y curl \ && rm -rf /src/*.deb From f0c10e3daf34e4c81707d2f26e2564e5911012fc Mon Sep 17 00:00:00 2001 From: George Griffiths Date: Tue, 12 May 2020 13:51:48 +0100 Subject: [PATCH 2/7] refactor playwright to fix custom locators with xpath --- lib/helper/Playwright.js | 56 ++++++++-------------- test/data/app/view/form/custom_locator.php | 56 ++++++++++++++++++++++ test/helper/Playwright_test.js | 2 +- test/helper/webapi.js | 54 +++++++++++++++++++++ 4 files changed, 132 insertions(+), 36 deletions(-) create mode 100644 test/data/app/view/form/custom_locator.php diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index fa3c56abf..1991c28cb 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -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}`); }); @@ -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}`); @@ -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'); } @@ -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}`); }); @@ -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}`); }); @@ -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}`); }); @@ -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}`); }); } @@ -2061,14 +2041,20 @@ 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()) { + // https://github.com/microsoft/playwright/issues/2197 + // 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 = {}) { diff --git a/test/data/app/view/form/custom_locator.php b/test/data/app/view/form/custom_locator.php new file mode 100644 index 000000000..d7ffadd1e --- /dev/null +++ b/test/data/app/view/form/custom_locator.php @@ -0,0 +1,56 @@ + + + + + + + +
Step One Button
+
Step Two Button
+
Step Three Button
+
Steps Complete!
+ + + + + diff --git a/test/helper/Playwright_test.js b/test/helper/Playwright_test.js index 5541999c5..7e74e49a8 100644 --- a/test/helper/Playwright_test.js +++ b/test/helper/Playwright_test.js @@ -27,7 +27,7 @@ describe('Playwright', function () { I = new Playwright({ url: siteUrl, windowSize: '500x700', - show: false, + show: true, waitForTimeout: 5000, waitForAction: 500, restart: true, diff --git a/test/helper/webapi.js b/test/helper/webapi.js index 2bd18abc2..bb741b855 100644 --- a/test/helper/webapi.js +++ b/test/helper/webapi.js @@ -6,6 +6,9 @@ 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 I; let data; let siteUrl; @@ -1320,4 +1323,55 @@ module.exports.tests = function () { }); }); }); + + describe('#customLocators', () => { + beforeEach(() => { + // reset custom locators + Locator.filters = []; + }); + 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'); + }); + }); }; From 9b4a8152cfe5ffa059eba07078aed7a00c3e48f9 Mon Sep 17 00:00:00 2001 From: George Griffiths Date: Tue, 12 May 2020 13:56:35 +0100 Subject: [PATCH 3/7] remove test modification --- test/helper/Playwright_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/helper/Playwright_test.js b/test/helper/Playwright_test.js index 7e74e49a8..5541999c5 100644 --- a/test/helper/Playwright_test.js +++ b/test/helper/Playwright_test.js @@ -27,7 +27,7 @@ describe('Playwright', function () { I = new Playwright({ url: siteUrl, windowSize: '500x700', - show: true, + show: false, waitForTimeout: 5000, waitForAction: 500, restart: true, From 562d1309211ef68ebfd4a64c33bb16d2150e1e85 Mon Sep 17 00:00:00 2001 From: George Griffiths Date: Tue, 12 May 2020 14:03:14 +0100 Subject: [PATCH 4/7] CI kick From 22d0c7344f2eedde027ca733ee6b1ffcf6d3211e Mon Sep 17 00:00:00 2001 From: George Griffiths Date: Tue, 12 May 2020 14:19:44 +0100 Subject: [PATCH 5/7] Apply suggestions from code review --- test/helper/webapi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/helper/webapi.js b/test/helper/webapi.js index bb741b855..8705180f0 100644 --- a/test/helper/webapi.js +++ b/test/helper/webapi.js @@ -1325,7 +1325,7 @@ module.exports.tests = function () { }); describe('#customLocators', () => { - beforeEach(() => { + afterEach(() => { // reset custom locators Locator.filters = []; }); From d60e3b81b6fcd40565d5d591162ca8bc43ea00ff Mon Sep 17 00:00:00 2001 From: George Griffiths Date: Wed, 13 May 2020 11:58:31 +0100 Subject: [PATCH 6/7] fix flakey unit tests --- lib/helper/Playwright.js | 1 - test/runner/allure_test.js | 26 ++++++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index 1991c28cb..16595dafc 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -2045,7 +2045,6 @@ function buildLocatorString(locator) { if (locator.isCustom()) { return `${locator.type}=${locator.value}`; } if (locator.isXPath()) { - // https://github.com/microsoft/playwright/issues/2197 // dont rely on heuristics of playwright for figuring out xpath return `xpath=${locator.value}`; } diff --git a/test/runner/allure_test.js b/test/runner/allure_test.js index 501443e30..0e9976bae 100644 --- a/test/runner/allure_test.js +++ b/test/runner/allure_test.js @@ -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(); }); }); @@ -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(); }); }); From 05678b8016c1c9a00491cccc04888deb74d79d93 Mon Sep 17 00:00:00 2001 From: George Griffiths Date: Wed, 13 May 2020 12:13:48 +0100 Subject: [PATCH 7/7] ensure dont reset Locators --- test/helper/webapi.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/helper/webapi.js b/test/helper/webapi.js index 8705180f0..717908beb 100644 --- a/test/helper/webapi.js +++ b/test/helper/webapi.js @@ -9,6 +9,7 @@ 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; @@ -1325,9 +1326,13 @@ module.exports.tests = function () { }); describe('#customLocators', () => { + beforeEach(() => { + originalLocators = Locator.filters; + Locator.filters = []; + }); afterEach(() => { // reset custom locators - Locator.filters = []; + Locator.filters = originalLocators; }); it('should support xpath custom locator by default', async () => { customLocators({